@refraction-ui/astro 0.11.1 → 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-empty-state/EmptyState.astro +57 -0
- package/dist/astro-empty-state/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-input/Input.astro +5 -1
- 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-password-input/PasswordInput.astro +102 -0
- package/dist/astro-password-input/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-segmented-control/SegmentedControl.astro +106 -0
- package/dist/astro-segmented-control/SegmentedControlItem.astro +32 -0
- package/dist/astro-segmented-control/index.ts +1 -0
- package/dist/astro-separator/Separator.astro +49 -0
- package/dist/astro-separator/index.ts +1 -0
- package/dist/astro-social-auth-button/SocialAuthButton.astro +110 -0
- package/dist/astro-social-auth-button/SocialAuthRow.astro +13 -0
- package/dist/astro-social-auth-button/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/empty-state/empty-state.styles.ts +42 -0
- package/dist/empty-state/empty-state.ts +27 -0
- package/dist/empty-state/index.ts +14 -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 +45 -17
- 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/input/index.ts +2 -0
- package/dist/input/input.styles.ts +4 -0
- package/dist/input/input.ts +39 -3
- 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/password-input/index.ts +9 -0
- package/dist/password-input/password-input.styles.ts +10 -0
- package/dist/password-input/password-input.ts +45 -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/segmented-control/index.ts +13 -0
- package/dist/segmented-control/segmented-control.styles.ts +42 -0
- package/dist/segmented-control/segmented-control.ts +80 -0
- package/dist/separator/index.ts +12 -0
- package/dist/separator/separator.styles.ts +34 -0
- package/dist/separator/separator.ts +53 -0
- package/dist/social-auth-button/index.ts +14 -0
- package/dist/social-auth-button/social-auth-button.styles.ts +16 -0
- package/dist/social-auth-button/social-auth-button.ts +51 -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 +47 -19
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** A single caption cue — may be interim (non-final) while speech is in flight. */
|
|
2
|
+
export interface CaptionCue {
|
|
3
|
+
/** Unique identifier for this cue. */
|
|
4
|
+
id: string
|
|
5
|
+
/** Speaker name, shown as a prefix when present. */
|
|
6
|
+
speaker?: string
|
|
7
|
+
/** The caption text. */
|
|
8
|
+
text: string
|
|
9
|
+
/** Whether this cue is finalised (true) or still being transcribed (false/absent). */
|
|
10
|
+
final?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Return the last `maxLines` cues to display in the caption overlay.
|
|
15
|
+
* Preserves ordering (oldest first within the slice) so the overlay reads top-to-bottom.
|
|
16
|
+
*/
|
|
17
|
+
export function visibleCues(cues: CaptionCue[], maxLines = 2): CaptionCue[] {
|
|
18
|
+
if (maxLines <= 0) return []
|
|
19
|
+
return cues.slice(-maxLines)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Format a single cue for display.
|
|
24
|
+
* When `speaker` is present the result is `'Maya: the bottleneck is review capacity'`.
|
|
25
|
+
* Without a speaker it is just the text.
|
|
26
|
+
*/
|
|
27
|
+
export function formatCue(cue: CaptionCue): string {
|
|
28
|
+
return cue.speaker ? `${cue.speaker}: ${cue.text}` : cue.text
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface LiveCaptionsAPI {
|
|
32
|
+
/** ARIA attributes to spread on the caption container element. */
|
|
33
|
+
ariaProps: Record<string, string | number | boolean>
|
|
34
|
+
/** Data attributes for styling hooks. */
|
|
35
|
+
dataAttributes: Record<string, string>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build the framework-agnostic accessibility props for a live-caption overlay.
|
|
40
|
+
*
|
|
41
|
+
* Returns `role="log"` with `aria-live="polite"` and `aria-atomic=false` so
|
|
42
|
+
* screen readers announce individual cues as they arrive without re-reading the
|
|
43
|
+
* whole log. Adapters spread these onto the container element.
|
|
44
|
+
*/
|
|
45
|
+
export function createLiveCaptions(): LiveCaptionsAPI {
|
|
46
|
+
const ariaProps: Record<string, string | number | boolean> = {
|
|
47
|
+
role: 'log',
|
|
48
|
+
'aria-live': 'polite',
|
|
49
|
+
'aria-atomic': false,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const dataAttributes: Record<string, string> = {
|
|
53
|
+
'data-component': 'live-captions',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { ariaProps, dataAttributes }
|
|
57
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createLiveCursors,
|
|
3
|
+
assignCursorColor,
|
|
4
|
+
CURSOR_COLORS,
|
|
5
|
+
type CursorData,
|
|
6
|
+
type LiveCursorsAPI,
|
|
7
|
+
} from './live-cursors.js'
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
liveCursorsVariants,
|
|
11
|
+
cursorWrapperClass,
|
|
12
|
+
cursorArrowClass,
|
|
13
|
+
cursorLabelClass,
|
|
14
|
+
} from './live-cursors.styles.js'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Full-bleed overlay that contains all cursor elements.
|
|
5
|
+
* `pointer-events-none` and `overflow-hidden` ensure cursors are decorative
|
|
6
|
+
* and don't interfere with the underlying canvas interactions.
|
|
7
|
+
*/
|
|
8
|
+
export const liveCursorsVariants = cva({
|
|
9
|
+
base: 'absolute inset-0 overflow-hidden pointer-events-none',
|
|
10
|
+
variants: {
|
|
11
|
+
/** Future variant hook — reserved for theming overrides. */
|
|
12
|
+
hidden: {
|
|
13
|
+
true: 'invisible',
|
|
14
|
+
false: '',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
defaultVariants: {
|
|
18
|
+
hidden: 'false',
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Wrapper for a single cursor (SVG + label). Absolutely positioned via inline
|
|
24
|
+
* `left`/`top` style; the transition provides smooth movement when coordinates
|
|
25
|
+
* update.
|
|
26
|
+
*/
|
|
27
|
+
export const cursorWrapperClass =
|
|
28
|
+
'absolute flex flex-col items-start transition-[left,top] duration-100 ease-linear'
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The SVG cursor arrow container. Fill is applied via inline style (per
|
|
32
|
+
* collaborator color) so no utility class is needed here.
|
|
33
|
+
*/
|
|
34
|
+
export const cursorArrowClass = 'h-5 w-5 drop-shadow-sm'
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Name-label chip that appears below the cursor arrow. Background is applied
|
|
38
|
+
* via inline style (per collaborator color); text is always white for contrast.
|
|
39
|
+
*/
|
|
40
|
+
export const cursorLabelClass =
|
|
41
|
+
'mt-0.5 ml-2 rounded px-1.5 py-0.5 text-xs font-medium text-white shadow-sm whitespace-nowrap'
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Data for a single collaborator cursor. */
|
|
2
|
+
export type CursorData = {
|
|
3
|
+
id: string
|
|
4
|
+
name: string
|
|
5
|
+
x: number
|
|
6
|
+
y: number
|
|
7
|
+
color?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A fixed palette of 8 hex colors for distinguishing collaborator cursors.
|
|
12
|
+
* Each color is vivid enough to be visible against both light and dark surfaces.
|
|
13
|
+
*/
|
|
14
|
+
export const CURSOR_COLORS: string[] = [
|
|
15
|
+
'#E94560', // red
|
|
16
|
+
'#F57C00', // orange
|
|
17
|
+
'#F9C840', // yellow
|
|
18
|
+
'#4CAF50', // green
|
|
19
|
+
'#00ACC1', // cyan
|
|
20
|
+
'#5C6BC0', // indigo
|
|
21
|
+
'#AB47BC', // purple
|
|
22
|
+
'#EC407A', // pink
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Assign a stable color to a cursor.
|
|
27
|
+
*
|
|
28
|
+
* When `index` is supplied the color is simply `CURSOR_COLORS[index % length]`
|
|
29
|
+
* (deterministic for ordered participant lists). Otherwise a lightweight
|
|
30
|
+
* djb2-style hash of the `id` string is used so the same id always maps to the
|
|
31
|
+
* same color across renders without using Math.random or Date.now.
|
|
32
|
+
*/
|
|
33
|
+
export function assignCursorColor(id: string, index?: number): string {
|
|
34
|
+
if (index !== undefined) {
|
|
35
|
+
return CURSOR_COLORS[index % CURSOR_COLORS.length]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// djb2 hash — deterministic, no side-effects
|
|
39
|
+
let hash = 5381
|
|
40
|
+
for (let i = 0; i < id.length; i++) {
|
|
41
|
+
hash = ((hash << 5) + hash) ^ id.charCodeAt(i)
|
|
42
|
+
hash = hash >>> 0 // keep unsigned 32-bit
|
|
43
|
+
}
|
|
44
|
+
return CURSOR_COLORS[hash % CURSOR_COLORS.length]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface LiveCursorsAPI {
|
|
48
|
+
/** ARIA attributes to spread on the overlay container. */
|
|
49
|
+
ariaProps: Record<string, string | number | boolean>
|
|
50
|
+
/** Data attributes for styling hooks. */
|
|
51
|
+
dataAttributes: Record<string, string>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build the framework-agnostic props for the live-cursors overlay.
|
|
56
|
+
*
|
|
57
|
+
* Cursors are purely decorative real-time presence indicators — aria-hidden
|
|
58
|
+
* true keeps them out of the accessibility tree. The group role is present for
|
|
59
|
+
* semantic correctness should a developer choose to un-hide it.
|
|
60
|
+
*/
|
|
61
|
+
export function createLiveCursors(): LiveCursorsAPI {
|
|
62
|
+
const ariaProps: Record<string, string | number | boolean> = {
|
|
63
|
+
role: 'group',
|
|
64
|
+
'aria-label': 'Collaborators',
|
|
65
|
+
'aria-hidden': true,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const dataAttributes: Record<string, string> = {
|
|
69
|
+
'data-live-cursors': '',
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { ariaProps, dataAttributes }
|
|
73
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export {
|
|
2
|
+
groupConsecutiveBySpeaker,
|
|
3
|
+
createLiveTranscript,
|
|
4
|
+
type TranscriptEntry,
|
|
5
|
+
type TranscriptBlock,
|
|
6
|
+
type LiveTranscriptAPI,
|
|
7
|
+
} from './live-transcript.js'
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
liveTranscriptVariants,
|
|
11
|
+
transcriptBlockVariants,
|
|
12
|
+
transcriptSpeakerRowClass,
|
|
13
|
+
transcriptSpeakerNameClass,
|
|
14
|
+
transcriptTimestampClass,
|
|
15
|
+
transcriptTextLineClass,
|
|
16
|
+
} from './live-transcript.styles.js'
|
|
@@ -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,10 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Positioning classes for the reveal/hide toggle button rendered inside a
|
|
5
|
+
* password input's trailing padding. The wrapper must be `relative` and the
|
|
6
|
+
* input must reserve trailing space (e.g. `pr-10`).
|
|
7
|
+
*/
|
|
8
|
+
export const passwordToggleVariants = cva({
|
|
9
|
+
base: 'absolute right-2 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
10
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface PasswordInputProps {
|
|
2
|
+
/** Whether the password is currently revealed (shown as plain text). */
|
|
3
|
+
revealed?: boolean
|
|
4
|
+
/**
|
|
5
|
+
* Accessible label for the reveal (show password) action.
|
|
6
|
+
* @default 'Show password'
|
|
7
|
+
*/
|
|
8
|
+
revealLabel?: string
|
|
9
|
+
/**
|
|
10
|
+
* Accessible label for the hide action.
|
|
11
|
+
* @default 'Hide password'
|
|
12
|
+
*/
|
|
13
|
+
hideLabel?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PasswordInputAPI {
|
|
17
|
+
/** The `type` attribute for the underlying input given the revealed state. */
|
|
18
|
+
inputType: 'text' | 'password'
|
|
19
|
+
/** The accessible label for the toggle button given the revealed state. */
|
|
20
|
+
toggleLabel: string
|
|
21
|
+
/** The `aria-pressed` value for the toggle button. */
|
|
22
|
+
togglePressed: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const DEFAULT_REVEAL_LABEL = 'Show password'
|
|
26
|
+
export const DEFAULT_HIDE_LABEL = 'Hide password'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* State-free password-input helper. Given a `revealed` flag (owned by the
|
|
30
|
+
* framework adapter), it returns the input `type`, the toggle button's
|
|
31
|
+
* accessible label, and its `aria-pressed` value. It does not hold state.
|
|
32
|
+
*/
|
|
33
|
+
export function createPasswordInput(props: PasswordInputProps = {}): PasswordInputAPI {
|
|
34
|
+
const {
|
|
35
|
+
revealed = false,
|
|
36
|
+
revealLabel = DEFAULT_REVEAL_LABEL,
|
|
37
|
+
hideLabel = DEFAULT_HIDE_LABEL,
|
|
38
|
+
} = props
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
inputType: revealed ? 'text' : 'password',
|
|
42
|
+
toggleLabel: revealed ? hideLabel : revealLabel,
|
|
43
|
+
togglePressed: revealed,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -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'
|