@refraction-ui/astro 0.12.0 → 0.13.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.
- package/dist/astro-audio-room/AudioRoom.astro +81 -0
- package/dist/astro-audio-room/index.ts +1 -0
- package/dist/astro-call-controls/CallControls.astro +57 -0
- package/dist/astro-call-controls/index.ts +1 -0
- package/dist/astro-checklist/Checklist.astro +76 -0
- package/dist/astro-checklist/index.ts +1 -0
- package/dist/astro-editor-status-bar/EditorStatusBar.astro +92 -0
- package/dist/astro-editor-status-bar/index.ts +1 -0
- package/dist/astro-editor-tabs/EditorTabs.astro +74 -0
- package/dist/astro-editor-tabs/index.ts +1 -0
- package/dist/astro-floating-reactions/FloatingReactions.astro +56 -0
- package/dist/astro-floating-reactions/index.ts +1 -0
- package/dist/astro-flow-editor/FlowEditor.astro +99 -0
- package/dist/astro-flow-editor/index.ts +3 -0
- package/dist/astro-graph-view/GraphView.astro +109 -0
- package/dist/astro-graph-view/index.ts +1 -0
- package/dist/astro-infinite-canvas/InfiniteCanvas.astro +43 -0
- package/dist/astro-infinite-canvas/index.ts +1 -0
- package/dist/astro-live-captions/LiveCaptions.astro +54 -0
- package/dist/astro-live-captions/index.ts +1 -0
- package/dist/astro-live-cursors/LiveCursors.astro +55 -0
- package/dist/astro-live-cursors/index.ts +1 -0
- package/dist/astro-live-transcript/LiveTranscript.astro +55 -0
- package/dist/astro-live-transcript/index.ts +1 -0
- package/dist/astro-mini-map/MiniMap.astro +74 -0
- package/dist/astro-mini-map/index.ts +1 -0
- package/dist/astro-pre-call-lobby/PreCallLobby.astro +202 -0
- package/dist/astro-pre-call-lobby/index.ts +1 -0
- package/dist/astro-radial-gauge/RadialGauge.astro +144 -0
- package/dist/astro-radial-gauge/index.ts +1 -0
- package/dist/astro-rating-scale/RatingScale.astro +73 -0
- package/dist/astro-rating-scale/index.ts +1 -0
- package/dist/astro-sticky-note/StickyNote.astro +55 -0
- package/dist/astro-sticky-note/index.ts +1 -0
- package/dist/astro-terminal/Terminal.astro +67 -0
- package/dist/astro-terminal/index.ts +1 -0
- package/dist/astro-test-results/TestResults.astro +76 -0
- package/dist/astro-test-results/index.ts +1 -0
- package/dist/astro-timeline/Timeline.astro +59 -0
- package/dist/astro-timeline/index.ts +1 -0
- package/dist/astro-video-grid/VideoGrid.astro +182 -0
- package/dist/astro-video-grid/index.ts +1 -0
- package/dist/astro-video-tile/VideoTile.astro +104 -0
- package/dist/astro-video-tile/index.ts +1 -0
- package/dist/astro-wizard/Wizard.astro +137 -0
- package/dist/astro-wizard/index.ts +1 -0
- package/dist/audio-room/audio-room.styles.ts +88 -0
- package/dist/audio-room/audio-room.ts +70 -0
- package/dist/audio-room/index.ts +16 -0
- package/dist/call-controls/call-controls.styles.ts +63 -0
- package/dist/call-controls/call-controls.ts +79 -0
- package/dist/call-controls/index.ts +15 -0
- package/dist/checklist/checklist.styles.ts +76 -0
- package/dist/checklist/checklist.ts +62 -0
- package/dist/checklist/index.ts +18 -0
- package/dist/editor-status-bar/editor-status-bar.styles.ts +40 -0
- package/dist/editor-status-bar/editor-status-bar.ts +78 -0
- package/dist/editor-status-bar/index.ts +15 -0
- package/dist/editor-tabs/editor-tabs.styles.ts +52 -0
- package/dist/editor-tabs/editor-tabs.ts +82 -0
- package/dist/editor-tabs/index.ts +15 -0
- package/dist/floating-reactions/floating-reactions.styles.ts +54 -0
- package/dist/floating-reactions/floating-reactions.ts +85 -0
- package/dist/floating-reactions/index.ts +14 -0
- package/dist/flow-editor/flow-editor.styles.ts +56 -0
- package/dist/flow-editor/flow-editor.ts +126 -0
- package/dist/flow-editor/index.ts +18 -0
- package/dist/graph-view/graph-view.styles.ts +66 -0
- package/dist/graph-view/graph-view.ts +130 -0
- package/dist/graph-view/index.ts +21 -0
- package/dist/index.ts +23 -0
- package/dist/infinite-canvas/index.ts +17 -0
- package/dist/infinite-canvas/infinite-canvas.styles.ts +49 -0
- package/dist/infinite-canvas/infinite-canvas.ts +97 -0
- package/dist/live-captions/index.ts +13 -0
- package/dist/live-captions/live-captions.styles.ts +45 -0
- package/dist/live-captions/live-captions.ts +57 -0
- package/dist/live-cursors/index.ts +14 -0
- package/dist/live-cursors/live-cursors.styles.ts +41 -0
- package/dist/live-cursors/live-cursors.ts +73 -0
- package/dist/live-transcript/index.ts +16 -0
- package/dist/live-transcript/live-transcript.styles.ts +48 -0
- package/dist/live-transcript/live-transcript.ts +82 -0
- package/dist/mini-map/index.ts +16 -0
- package/dist/mini-map/mini-map.styles.ts +32 -0
- package/dist/mini-map/mini-map.ts +122 -0
- package/dist/pre-call-lobby/index.ts +22 -0
- package/dist/pre-call-lobby/pre-call-lobby.styles.ts +108 -0
- package/dist/pre-call-lobby/pre-call-lobby.ts +61 -0
- package/dist/radial-gauge/index.ts +26 -0
- package/dist/radial-gauge/radial-gauge.styles.ts +75 -0
- package/dist/radial-gauge/radial-gauge.ts +183 -0
- package/dist/rating-scale/index.ts +16 -0
- package/dist/rating-scale/rating-scale.styles.ts +48 -0
- package/dist/rating-scale/rating-scale.ts +101 -0
- package/dist/sticky-note/index.ts +14 -0
- package/dist/sticky-note/sticky-note.styles.ts +37 -0
- package/dist/sticky-note/sticky-note.ts +47 -0
- package/dist/terminal/index.ts +13 -0
- package/dist/terminal/terminal.styles.ts +56 -0
- package/dist/terminal/terminal.ts +60 -0
- package/dist/test-results/index.ts +19 -0
- package/dist/test-results/test-results.styles.ts +93 -0
- package/dist/test-results/test-results.ts +83 -0
- package/dist/timeline/index.ts +22 -0
- package/dist/timeline/timeline.styles.ts +98 -0
- package/dist/timeline/timeline.ts +86 -0
- package/dist/video-grid/index.ts +16 -0
- package/dist/video-grid/video-grid.styles.ts +38 -0
- package/dist/video-grid/video-grid.ts +93 -0
- package/dist/video-tile/index.ts +17 -0
- package/dist/video-tile/video-tile.styles.ts +59 -0
- package/dist/video-tile/video-tile.ts +74 -0
- package/dist/wizard/index.ts +28 -0
- package/dist/wizard/wizard.styles.ts +147 -0
- package/dist/wizard/wizard.ts +128 -0
- package/package.json +24 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Scroll container that wraps all speaker blocks.
|
|
5
|
+
* The `compact` variant tightens the gap between blocks.
|
|
6
|
+
*/
|
|
7
|
+
export const liveTranscriptVariants = cva({
|
|
8
|
+
base: 'flex flex-col overflow-y-auto p-4 gap-4',
|
|
9
|
+
variants: {
|
|
10
|
+
compact: {
|
|
11
|
+
true: 'gap-2 p-3',
|
|
12
|
+
false: '',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
compact: 'false',
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
/** A single grouped speaker block (header + text lines). */
|
|
21
|
+
export const transcriptBlockVariants = cva({
|
|
22
|
+
base: 'flex flex-col gap-1',
|
|
23
|
+
variants: {
|
|
24
|
+
compact: {
|
|
25
|
+
true: 'gap-0.5',
|
|
26
|
+
false: '',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
compact: 'false',
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
/** The row containing the speaker name and optional timestamp. */
|
|
35
|
+
export const transcriptSpeakerRowClass =
|
|
36
|
+
'flex items-baseline gap-2'
|
|
37
|
+
|
|
38
|
+
/** Speaker name badge / label. */
|
|
39
|
+
export const transcriptSpeakerNameClass =
|
|
40
|
+
'text-sm font-semibold text-foreground leading-snug'
|
|
41
|
+
|
|
42
|
+
/** Timestamp displayed beside the speaker name. */
|
|
43
|
+
export const transcriptTimestampClass =
|
|
44
|
+
'text-xs text-muted-foreground tabular-nums'
|
|
45
|
+
|
|
46
|
+
/** A single line of transcript text. */
|
|
47
|
+
export const transcriptTextLineClass =
|
|
48
|
+
'text-sm text-foreground/80 leading-relaxed'
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** A single transcript entry from one speaker at a point in time. */
|
|
2
|
+
export interface TranscriptEntry {
|
|
3
|
+
/** Unique identifier for this entry. */
|
|
4
|
+
id: string
|
|
5
|
+
/** Display name of the speaker. */
|
|
6
|
+
speaker: string
|
|
7
|
+
/** The spoken text. */
|
|
8
|
+
text: string
|
|
9
|
+
/** Optional formatted timestamp string (e.g. "0:42"). */
|
|
10
|
+
timestamp?: string
|
|
11
|
+
/** Optional CSS color for the speaker's name (e.g. "#3b82f6"). */
|
|
12
|
+
speakerColor?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A merged block of consecutive entries from the same speaker. */
|
|
16
|
+
export interface TranscriptBlock {
|
|
17
|
+
/** Speaker display name. */
|
|
18
|
+
speaker: string
|
|
19
|
+
/** Timestamp of the first entry in the run (if present). */
|
|
20
|
+
timestamp?: string
|
|
21
|
+
/** Optional CSS color for the speaker name. */
|
|
22
|
+
speakerColor?: string
|
|
23
|
+
/** All text segments from the consecutive run, in order. */
|
|
24
|
+
texts: string[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Merge consecutive `TranscriptEntry` items from the same speaker into
|
|
29
|
+
* `TranscriptBlock` objects. A new block is started whenever the speaker
|
|
30
|
+
* changes; the block uses the timestamp of the *first* entry in that run.
|
|
31
|
+
*/
|
|
32
|
+
export function groupConsecutiveBySpeaker(
|
|
33
|
+
entries: TranscriptEntry[],
|
|
34
|
+
): TranscriptBlock[] {
|
|
35
|
+
const blocks: TranscriptBlock[] = []
|
|
36
|
+
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
const last = blocks[blocks.length - 1]
|
|
39
|
+
if (last && last.speaker === entry.speaker) {
|
|
40
|
+
last.texts.push(entry.text)
|
|
41
|
+
} else {
|
|
42
|
+
blocks.push({
|
|
43
|
+
speaker: entry.speaker,
|
|
44
|
+
timestamp: entry.timestamp,
|
|
45
|
+
speakerColor: entry.speakerColor,
|
|
46
|
+
texts: [entry.text],
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return blocks
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface LiveTranscriptAPI {
|
|
55
|
+
/**
|
|
56
|
+
* ARIA attributes to spread on the scroll container.
|
|
57
|
+
* Typed as a plain record so frameworks can spread without fighting their
|
|
58
|
+
* aria-* type narrowing.
|
|
59
|
+
*/
|
|
60
|
+
ariaProps: Record<string, string | number | boolean>
|
|
61
|
+
/** Data attributes for styling hooks. */
|
|
62
|
+
dataAttributes: Record<string, string>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Build the framework-agnostic container props for a live transcript panel.
|
|
67
|
+
*
|
|
68
|
+
* Returns `role="log"` and `aria-live="polite"` so screen readers announce
|
|
69
|
+
* new transcript entries without interrupting the user.
|
|
70
|
+
*/
|
|
71
|
+
export function createLiveTranscript(): LiveTranscriptAPI {
|
|
72
|
+
const ariaProps: Record<string, string | number | boolean> = {
|
|
73
|
+
role: 'log',
|
|
74
|
+
'aria-live': 'polite',
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const dataAttributes: Record<string, string> = {
|
|
78
|
+
'data-component': 'live-transcript',
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { ariaProps, dataAttributes }
|
|
82
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export {
|
|
2
|
+
contentBounds,
|
|
3
|
+
miniScale,
|
|
4
|
+
worldToMini,
|
|
5
|
+
viewportRectInMini,
|
|
6
|
+
createMiniMap,
|
|
7
|
+
type Rect,
|
|
8
|
+
type MiniMapItem,
|
|
9
|
+
type MiniMapAPI,
|
|
10
|
+
} from './mini-map.js'
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
miniMapVariants,
|
|
14
|
+
miniMapDotClass,
|
|
15
|
+
miniMapViewportClass,
|
|
16
|
+
} from './mini-map.styles.js'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The outer minimap box: small, rounded, bordered, card background.
|
|
5
|
+
* The `interactive` variant switches to grab cursor for clickable minimaps.
|
|
6
|
+
*/
|
|
7
|
+
export const miniMapVariants = cva({
|
|
8
|
+
base: [
|
|
9
|
+
'relative overflow-hidden rounded-md border border-border bg-card',
|
|
10
|
+
'select-none',
|
|
11
|
+
].join(' '),
|
|
12
|
+
variants: {
|
|
13
|
+
interactive: {
|
|
14
|
+
true: 'cursor-grab active:cursor-grabbing',
|
|
15
|
+
false: '',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
interactive: 'false',
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
/** A single node dot in the minimap (represents one `MiniMapItem`). */
|
|
24
|
+
export const miniMapDotClass =
|
|
25
|
+
'absolute rounded-sm bg-muted'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The draggable viewport indicator rectangle.
|
|
29
|
+
* Uses a semi-transparent primary fill with a primary ring/border.
|
|
30
|
+
*/
|
|
31
|
+
export const miniMapViewportClass =
|
|
32
|
+
'absolute rounded-sm border border-primary ring-1 ring-primary bg-primary/10'
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** A rectangle in 2-D space. */
|
|
2
|
+
export interface Rect {
|
|
3
|
+
x: number
|
|
4
|
+
y: number
|
|
5
|
+
width: number
|
|
6
|
+
height: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** A single item to be represented as a dot on the minimap. */
|
|
10
|
+
export interface MiniMapItem {
|
|
11
|
+
id: string
|
|
12
|
+
x: number
|
|
13
|
+
y: number
|
|
14
|
+
width?: number
|
|
15
|
+
height?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface MiniMapAPI {
|
|
19
|
+
/** ARIA attributes to spread on the minimap container element. */
|
|
20
|
+
ariaProps: { role: 'img'; 'aria-label': string }
|
|
21
|
+
/** Data attributes for styling hooks. */
|
|
22
|
+
dataAttributes: Record<string, string>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Compute the bounding box that encloses all items (using their x/y + optional
|
|
27
|
+
* width/height). Returns a zero-size rect at the origin when `items` is empty.
|
|
28
|
+
*/
|
|
29
|
+
export function contentBounds(items: MiniMapItem[]): Rect {
|
|
30
|
+
if (items.length === 0) return { x: 0, y: 0, width: 0, height: 0 }
|
|
31
|
+
|
|
32
|
+
let minX = Infinity
|
|
33
|
+
let minY = Infinity
|
|
34
|
+
let maxX = -Infinity
|
|
35
|
+
let maxY = -Infinity
|
|
36
|
+
|
|
37
|
+
for (const item of items) {
|
|
38
|
+
const right = item.x + (item.width ?? 0)
|
|
39
|
+
const bottom = item.y + (item.height ?? 0)
|
|
40
|
+
if (item.x < minX) minX = item.x
|
|
41
|
+
if (item.y < minY) minY = item.y
|
|
42
|
+
if (right > maxX) maxX = right
|
|
43
|
+
if (bottom > maxY) maxY = bottom
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Compute the uniform scale factor that fits `contentRect` into the minimap
|
|
51
|
+
* box (after subtracting `padding` on each side). Returns 1 when the content
|
|
52
|
+
* has zero area (nothing to scale).
|
|
53
|
+
*/
|
|
54
|
+
export function miniScale(
|
|
55
|
+
contentRect: Rect,
|
|
56
|
+
miniW: number,
|
|
57
|
+
miniH: number,
|
|
58
|
+
padding = 8,
|
|
59
|
+
): number {
|
|
60
|
+
const availW = miniW - padding * 2
|
|
61
|
+
const availH = miniH - padding * 2
|
|
62
|
+
if (availW <= 0 || availH <= 0) return 1
|
|
63
|
+
if (contentRect.width === 0 || contentRect.height === 0) return 1
|
|
64
|
+
return Math.min(availW / contentRect.width, availH / contentRect.height)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Map a world-space point `{ x, y }` to minimap pixel coordinates.
|
|
69
|
+
*
|
|
70
|
+
* @param point World coordinate
|
|
71
|
+
* @param contentRect Bounding box of all content (from `contentBounds`)
|
|
72
|
+
* @param scale Scale factor (from `miniScale`)
|
|
73
|
+
* @param padding Padding inset in px (must match the value passed to `miniScale`)
|
|
74
|
+
*/
|
|
75
|
+
export function worldToMini(
|
|
76
|
+
point: { x: number; y: number },
|
|
77
|
+
contentRect: Rect,
|
|
78
|
+
scale: number,
|
|
79
|
+
padding = 8,
|
|
80
|
+
): { x: number; y: number } {
|
|
81
|
+
return {
|
|
82
|
+
x: (point.x - contentRect.x) * scale + padding,
|
|
83
|
+
y: (point.y - contentRect.y) * scale + padding,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Compute the position and size of the viewport indicator rectangle in
|
|
89
|
+
* minimap coordinates, ready to be applied as CSS `left/top/width/height`.
|
|
90
|
+
*/
|
|
91
|
+
export function viewportRectInMini(
|
|
92
|
+
viewport: Rect,
|
|
93
|
+
contentRect: Rect,
|
|
94
|
+
scale: number,
|
|
95
|
+
padding = 8,
|
|
96
|
+
): Rect {
|
|
97
|
+
const { x, y } = worldToMini(
|
|
98
|
+
{ x: viewport.x, y: viewport.y },
|
|
99
|
+
contentRect,
|
|
100
|
+
scale,
|
|
101
|
+
padding,
|
|
102
|
+
)
|
|
103
|
+
return {
|
|
104
|
+
x,
|
|
105
|
+
y,
|
|
106
|
+
width: viewport.width * scale,
|
|
107
|
+
height: viewport.height * scale,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Build the framework-agnostic ARIA props for the minimap container.
|
|
113
|
+
*
|
|
114
|
+
* Every adapter spreads `ariaProps` onto its outermost element; `dataAttributes`
|
|
115
|
+
* are available for CSS hooks.
|
|
116
|
+
*/
|
|
117
|
+
export function createMiniMap(): MiniMapAPI {
|
|
118
|
+
return {
|
|
119
|
+
ariaProps: { role: 'img', 'aria-label': 'Minimap' },
|
|
120
|
+
dataAttributes: { 'data-component': 'mini-map' },
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createPreCallLobby,
|
|
3
|
+
micLevelToBars,
|
|
4
|
+
type MediaDeviceOption,
|
|
5
|
+
type DeviceKind,
|
|
6
|
+
type PreCallLobbyProps,
|
|
7
|
+
type PreCallLobbyAPI,
|
|
8
|
+
} from './pre-call-lobby.js'
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
preCallLobbyVariants,
|
|
12
|
+
preCallCameraVariants,
|
|
13
|
+
preCallCameraPlaceholderClass,
|
|
14
|
+
preCallDeviceRowClass,
|
|
15
|
+
preCallDeviceLabelClass,
|
|
16
|
+
preCallDeviceSelectClass,
|
|
17
|
+
preCallMicMeterClass,
|
|
18
|
+
preCallMicBarVariants,
|
|
19
|
+
preCallControlsRowClass,
|
|
20
|
+
preCallToggleVariants,
|
|
21
|
+
preCallJoinButtonClass,
|
|
22
|
+
} from './pre-call-lobby.styles.js'
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Outer lobby card — a centered panel that holds all preflight controls.
|
|
5
|
+
*/
|
|
6
|
+
export const preCallLobbyVariants = cva({
|
|
7
|
+
base: [
|
|
8
|
+
'flex flex-col gap-6 rounded-2xl border border-border bg-card p-6 shadow-sm',
|
|
9
|
+
'w-full max-w-lg mx-auto',
|
|
10
|
+
].join(' '),
|
|
11
|
+
variants: {},
|
|
12
|
+
defaultVariants: {},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Camera preview area. The `camera` variant switches between the live preview
|
|
17
|
+
* slot and an avatar / placeholder shown when the camera is off.
|
|
18
|
+
*/
|
|
19
|
+
export const preCallCameraVariants = cva({
|
|
20
|
+
base: [
|
|
21
|
+
'relative flex items-center justify-center overflow-hidden rounded-xl',
|
|
22
|
+
'aspect-video w-full bg-muted',
|
|
23
|
+
].join(' '),
|
|
24
|
+
variants: {
|
|
25
|
+
camera: {
|
|
26
|
+
on: '',
|
|
27
|
+
off: 'bg-muted',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
camera: 'on',
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
/** Placeholder shown inside the camera area when the camera is off. */
|
|
36
|
+
export const preCallCameraPlaceholderClass = [
|
|
37
|
+
'flex flex-col items-center justify-center gap-2',
|
|
38
|
+
'text-muted-foreground select-none',
|
|
39
|
+
].join(' ')
|
|
40
|
+
|
|
41
|
+
/** Row that holds the label and the <select> for a device picker. */
|
|
42
|
+
export const preCallDeviceRowClass = 'flex flex-col gap-1'
|
|
43
|
+
|
|
44
|
+
/** Label text above a device picker. */
|
|
45
|
+
export const preCallDeviceLabelClass = 'text-xs font-medium text-muted-foreground'
|
|
46
|
+
|
|
47
|
+
/** The <select> element used for device selection. */
|
|
48
|
+
export const preCallDeviceSelectClass = [
|
|
49
|
+
'flex h-9 w-full rounded-md border border-input bg-background px-3 py-1',
|
|
50
|
+
'text-sm text-foreground shadow-sm',
|
|
51
|
+
'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
|
52
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
53
|
+
].join(' ')
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Mic level meter container. Renders a row of bars.
|
|
57
|
+
*/
|
|
58
|
+
export const preCallMicMeterClass = 'flex items-end gap-0.5 h-5'
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* An individual bar in the mic level meter.
|
|
62
|
+
* The `lit` variant distinguishes active (lit) from inactive bars.
|
|
63
|
+
*/
|
|
64
|
+
export const preCallMicBarVariants = cva({
|
|
65
|
+
base: 'w-1.5 rounded-sm transition-colors',
|
|
66
|
+
variants: {
|
|
67
|
+
lit: {
|
|
68
|
+
true: 'bg-primary',
|
|
69
|
+
false: 'bg-muted-foreground/30',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
defaultVariants: {
|
|
73
|
+
lit: 'false',
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
/** Row holding the camera-toggle and mic-toggle buttons. */
|
|
78
|
+
export const preCallControlsRowClass = 'flex items-center gap-3'
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Toggle button for camera or microphone.
|
|
82
|
+
* The `active` variant styles the enabled vs disabled states.
|
|
83
|
+
*/
|
|
84
|
+
export const preCallToggleVariants = cva({
|
|
85
|
+
base: [
|
|
86
|
+
'inline-flex items-center justify-center rounded-full p-2.5',
|
|
87
|
+
'transition-colors focus-visible:outline-none focus-visible:ring-2',
|
|
88
|
+
'focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
89
|
+
].join(' '),
|
|
90
|
+
variants: {
|
|
91
|
+
active: {
|
|
92
|
+
true: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
93
|
+
false: 'bg-destructive/10 text-destructive hover:bg-destructive/20',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
defaultVariants: {
|
|
97
|
+
active: 'false',
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
/** Primary "Join" button. */
|
|
102
|
+
export const preCallJoinButtonClass = [
|
|
103
|
+
'inline-flex w-full items-center justify-center rounded-lg px-4 py-2.5',
|
|
104
|
+
'bg-primary text-primary-foreground text-sm font-semibold',
|
|
105
|
+
'shadow transition-colors hover:bg-primary/90',
|
|
106
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
107
|
+
'disabled:pointer-events-none disabled:opacity-50',
|
|
108
|
+
].join(' ')
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** A single selectable media device (camera, microphone, or speaker). */
|
|
2
|
+
export interface MediaDeviceOption {
|
|
3
|
+
id: string
|
|
4
|
+
label: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** The kind of media device. */
|
|
8
|
+
export type DeviceKind = 'camera' | 'microphone' | 'speaker'
|
|
9
|
+
|
|
10
|
+
export interface PreCallLobbyProps {
|
|
11
|
+
/** Whether the camera is currently enabled. */
|
|
12
|
+
cameraOn: boolean
|
|
13
|
+
/** Whether the microphone is currently enabled. */
|
|
14
|
+
micOn: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PreCallLobbyAPI {
|
|
18
|
+
/** ARIA attributes to spread on the group element. */
|
|
19
|
+
ariaProps: { role: 'group'; 'aria-label': string }
|
|
20
|
+
/** Data attributes for styling hooks. */
|
|
21
|
+
dataAttributes: { 'data-camera': string; 'data-mic': string }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Build the framework-agnostic group props for a pre-call lobby / device-check panel.
|
|
26
|
+
*
|
|
27
|
+
* Returns `role="group"` plus data attributes reflecting camera and mic state.
|
|
28
|
+
* Adapters spread these onto their container and render the device pickers,
|
|
29
|
+
* preview, and join button themselves.
|
|
30
|
+
*/
|
|
31
|
+
export function createPreCallLobby(props: PreCallLobbyProps): PreCallLobbyAPI {
|
|
32
|
+
const { cameraOn, micOn } = props
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
ariaProps: {
|
|
36
|
+
role: 'group',
|
|
37
|
+
'aria-label': 'Device setup',
|
|
38
|
+
},
|
|
39
|
+
dataAttributes: {
|
|
40
|
+
'data-camera': cameraOn ? 'on' : 'off',
|
|
41
|
+
'data-mic': micOn ? 'on' : 'off',
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Convert a microphone level (0..1) to a count of lit bars.
|
|
48
|
+
*
|
|
49
|
+
* Pure function — no side effects, no JSX. Adapters use this to render
|
|
50
|
+
* the mic level meter by iterating `0..barCount` and lighting bars up to
|
|
51
|
+
* the returned value.
|
|
52
|
+
*
|
|
53
|
+
* @param level Current mic level in the range [0, 1].
|
|
54
|
+
* @param barCount Total number of bars in the meter.
|
|
55
|
+
* @returns Number of bars that should appear "lit" (0..barCount).
|
|
56
|
+
*/
|
|
57
|
+
export function micLevelToBars(level: number, barCount: number): number {
|
|
58
|
+
if (barCount <= 0) return 0
|
|
59
|
+
const clamped = Math.min(1, Math.max(0, level))
|
|
60
|
+
return Math.round(clamped * barCount)
|
|
61
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export {
|
|
2
|
+
clampValue,
|
|
3
|
+
valueToFraction,
|
|
4
|
+
polarToCartesian,
|
|
5
|
+
describeArc,
|
|
6
|
+
gaugeStrokeDashoffset,
|
|
7
|
+
resolveZoneTone,
|
|
8
|
+
createRadialGauge,
|
|
9
|
+
GAUGE_SIZE_PX,
|
|
10
|
+
type GaugeSize,
|
|
11
|
+
type GaugeTone,
|
|
12
|
+
type GaugeZone,
|
|
13
|
+
type RadialGaugeProps,
|
|
14
|
+
type RadialGaugeAPI,
|
|
15
|
+
} from './radial-gauge.js'
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
radialGaugeRootVariants,
|
|
19
|
+
gaugeTrackClass,
|
|
20
|
+
gaugeArcClass,
|
|
21
|
+
gaugeArcToneVariants,
|
|
22
|
+
gaugeLabelClass,
|
|
23
|
+
gaugeSublabelClass,
|
|
24
|
+
GAUGE_LABEL_FONT_SIZE,
|
|
25
|
+
GAUGE_SUBLABEL_FONT_SIZE,
|
|
26
|
+
} from './radial-gauge.styles.js'
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Root SVG element variants — controls the rendered size via an inline
|
|
5
|
+
* width/height (adapters use GAUGE_SIZE_PX) and sets up the color context.
|
|
6
|
+
*/
|
|
7
|
+
export const radialGaugeRootVariants = cva({
|
|
8
|
+
base: 'inline-block shrink-0',
|
|
9
|
+
variants: {
|
|
10
|
+
size: {
|
|
11
|
+
sm: '',
|
|
12
|
+
md: '',
|
|
13
|
+
lg: '',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
size: 'md',
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Track (background) circle stroke class.
|
|
23
|
+
* Uses a muted foreground so it sits behind the value arc without competing.
|
|
24
|
+
*/
|
|
25
|
+
export const gaugeTrackClass = 'fill-none stroke-muted-foreground/20'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Value arc stroke class — resolved per tone via {@link gaugeArcToneVariants}.
|
|
29
|
+
* `currentColor` stroke picks up the Tailwind text-* class applied by the
|
|
30
|
+
* tone variant so we only need one class-generating helper.
|
|
31
|
+
*/
|
|
32
|
+
export const gaugeArcClass = 'fill-none stroke-current transition-[stroke-dashoffset] duration-300 ease-out'
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Tone variants for the value arc. Adapters combine this with `gaugeArcClass`.
|
|
36
|
+
*/
|
|
37
|
+
export const gaugeArcToneVariants = cva({
|
|
38
|
+
base: '',
|
|
39
|
+
variants: {
|
|
40
|
+
tone: {
|
|
41
|
+
default: 'text-foreground',
|
|
42
|
+
success: 'text-emerald-500',
|
|
43
|
+
warning: 'text-amber-500',
|
|
44
|
+
danger: 'text-destructive',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
defaultVariants: {
|
|
48
|
+
tone: 'default',
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
/** Center label (primary text) classes. */
|
|
53
|
+
export const gaugeLabelClass = 'fill-foreground text-center font-semibold'
|
|
54
|
+
|
|
55
|
+
/** Center sub-label (secondary text) classes. */
|
|
56
|
+
export const gaugeSublabelClass = 'fill-muted-foreground'
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Font size for the primary label by size variant.
|
|
60
|
+
* Expressed as SVG `font-size` values (px) for use in the `<text>` element.
|
|
61
|
+
*/
|
|
62
|
+
export const GAUGE_LABEL_FONT_SIZE: Record<'sm' | 'md' | 'lg', number> = {
|
|
63
|
+
sm: 14,
|
|
64
|
+
md: 20,
|
|
65
|
+
lg: 26,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Font size for the sub-label by size variant.
|
|
70
|
+
*/
|
|
71
|
+
export const GAUGE_SUBLABEL_FONT_SIZE: Record<'sm' | 'md' | 'lg', number> = {
|
|
72
|
+
sm: 9,
|
|
73
|
+
md: 11,
|
|
74
|
+
lg: 13,
|
|
75
|
+
}
|