@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.
- package/dist/astro-audience-feature-card/AudienceFeatureCard.astro +53 -0
- package/dist/astro-audience-feature-card/index.ts +1 -0
- package/dist/astro-audio-room/AudioRoom.astro +81 -0
- package/dist/astro-audio-room/index.ts +1 -0
- package/dist/astro-brand-network-cell/BrandNetworkCell.astro +77 -0
- package/dist/astro-brand-network-cell/index.ts +1 -0
- package/dist/astro-browser-chrome-mock/BrowserChromeMock.astro +62 -0
- package/dist/astro-browser-chrome-mock/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-marquee-strip/MarqueeStrip.astro +63 -0
- package/dist/astro-marquee-strip/index.ts +1 -0
- package/dist/astro-mastery-bar/MasteryBar.astro +56 -0
- package/dist/astro-mastery-bar/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-numbered-steps/NumberedSteps.astro +47 -0
- package/dist/astro-numbered-steps/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-pricing-card/PricingCard.astro +114 -0
- package/dist/astro-pricing-card/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-section-head/SectionHead.astro +44 -0
- package/dist/astro-section-head/index.ts +1 -0
- package/dist/astro-stat-grid/StatGrid.astro +46 -0
- package/dist/astro-stat-grid/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/audience-feature-card/audience-feature-card.styles.ts +23 -0
- package/dist/audience-feature-card/audience-feature-card.ts +39 -0
- package/dist/audience-feature-card/index.ts +13 -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/brand-network-cell/brand-network-cell.styles.ts +46 -0
- package/dist/brand-network-cell/brand-network-cell.ts +35 -0
- package/dist/brand-network-cell/index.ts +14 -0
- package/dist/browser-chrome-mock/browser-chrome-mock.styles.ts +43 -0
- package/dist/browser-chrome-mock/browser-chrome-mock.ts +52 -0
- package/dist/browser-chrome-mock/index.ts +17 -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 +32 -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/marquee-strip/index.ts +14 -0
- package/dist/marquee-strip/marquee-strip.styles.ts +61 -0
- package/dist/marquee-strip/marquee-strip.ts +35 -0
- package/dist/mastery-bar/index.ts +15 -0
- package/dist/mastery-bar/mastery-bar.styles.ts +45 -0
- package/dist/mastery-bar/mastery-bar.ts +55 -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/numbered-steps/index.ts +16 -0
- package/dist/numbered-steps/numbered-steps.styles.ts +36 -0
- package/dist/numbered-steps/numbered-steps.ts +64 -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/pricing-card/index.ts +19 -0
- package/dist/pricing-card/pricing-card.styles.ts +71 -0
- package/dist/pricing-card/pricing-card.ts +37 -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/section-head/index.ts +13 -0
- package/dist/section-head/section-head.styles.ts +30 -0
- package/dist/section-head/section-head.ts +28 -0
- package/dist/stat-grid/index.ts +13 -0
- package/dist/stat-grid/stat-grid.styles.ts +25 -0
- package/dist/stat-grid/stat-grid.ts +41 -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 +33 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Caption overlay container — an absolute-friendly, translucent dark surface
|
|
5
|
+
* centred over its positioned ancestor (e.g. a video element).
|
|
6
|
+
*/
|
|
7
|
+
export const liveCaptionsVariants = cva({
|
|
8
|
+
base: [
|
|
9
|
+
'relative mx-auto w-full max-w-2xl rounded-xl',
|
|
10
|
+
'bg-card/80 backdrop-blur-sm',
|
|
11
|
+
'px-4 py-3 space-y-1',
|
|
12
|
+
].join(' '),
|
|
13
|
+
variants: {
|
|
14
|
+
/** Controls how the container is positioned in the layout. */
|
|
15
|
+
position: {
|
|
16
|
+
/** Sits in normal document flow (default). */
|
|
17
|
+
static: '',
|
|
18
|
+
/** Absolutely positioned — consumer places it with inset utilities. */
|
|
19
|
+
absolute: 'absolute',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
position: 'static',
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A single cue line inside the overlay.
|
|
29
|
+
* Non-final (interim) cues get a dimmed italic treatment to signal they may change.
|
|
30
|
+
*/
|
|
31
|
+
export const liveCaptionsCueVariants = cva({
|
|
32
|
+
base: 'text-sm leading-snug text-foreground',
|
|
33
|
+
variants: {
|
|
34
|
+
interim: {
|
|
35
|
+
true: 'opacity-60 italic',
|
|
36
|
+
false: '',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
defaultVariants: {
|
|
40
|
+
interim: 'false',
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
/** Speaker-name prefix — rendered slightly muted to distinguish it from the speech text. */
|
|
45
|
+
export const liveCaptionsSpeakerClass = 'font-medium text-muted-foreground'
|
|
@@ -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,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createMarqueeStrip,
|
|
3
|
+
type MarqueeStripProps,
|
|
4
|
+
type MarqueeStripAPI,
|
|
5
|
+
} from './marquee-strip.js'
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
marqueeStripVariants,
|
|
9
|
+
marqueeStripInnerClass,
|
|
10
|
+
marqueeStripScrollOuterClass,
|
|
11
|
+
marqueeStripScrollTrackClass,
|
|
12
|
+
marqueeStripLabelClass,
|
|
13
|
+
marqueeStripItemClass,
|
|
14
|
+
} from './marquee-strip.styles.js'
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Container — a full-width band with a subtle muted background and border.
|
|
5
|
+
*
|
|
6
|
+
* The `scroll` variant switches the inner track from wrapping flex to an
|
|
7
|
+
* overflow-hidden marquee track. See {@link marqueeStripInnerClass} and
|
|
8
|
+
* {@link marqueeStripScrollTrackClass} for the corresponding track classes.
|
|
9
|
+
*/
|
|
10
|
+
export const marqueeStripVariants = cva({
|
|
11
|
+
base: 'w-full bg-muted/30 border-y border-border py-3',
|
|
12
|
+
variants: {
|
|
13
|
+
scroll: {
|
|
14
|
+
true: '',
|
|
15
|
+
false: '',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
scroll: 'false',
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Inner wrapper for static (non-scroll) mode.
|
|
25
|
+
*
|
|
26
|
+
* Items wrap naturally so they never overflow on narrow viewports.
|
|
27
|
+
*/
|
|
28
|
+
export const marqueeStripInnerClass =
|
|
29
|
+
'flex flex-wrap items-center gap-3 px-4'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Outer clip track for scroll mode — hides overflow so the looping animation
|
|
33
|
+
* is invisible at the edges.
|
|
34
|
+
*/
|
|
35
|
+
export const marqueeStripScrollOuterClass = 'overflow-hidden px-4'
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Moving track for scroll mode.
|
|
39
|
+
*
|
|
40
|
+
* The `marquee` keyframe must be defined in the consuming app's global CSS
|
|
41
|
+
* (or via a Tailwind plugin). The canonical definition is:
|
|
42
|
+
*
|
|
43
|
+
* ```css
|
|
44
|
+
* @keyframes marquee {
|
|
45
|
+
* from { transform: translateX(0); }
|
|
46
|
+
* to { transform: translateX(-50%); }
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* Items are duplicated once by the adapter so the strip appears seamless
|
|
51
|
+
* (the track is effectively 200 % wide and translates to –50 %).
|
|
52
|
+
*/
|
|
53
|
+
export const marqueeStripScrollTrackClass =
|
|
54
|
+
'flex items-center gap-3 w-max animate-[marquee_30s_linear_infinite]'
|
|
55
|
+
|
|
56
|
+
/** Strip label — shown before the items (uppercase eyebrow style). */
|
|
57
|
+
export const marqueeStripLabelClass =
|
|
58
|
+
'text-xs font-semibold uppercase tracking-widest text-muted-foreground shrink-0'
|
|
59
|
+
|
|
60
|
+
/** Individual item text. */
|
|
61
|
+
export const marqueeStripItemClass = 'text-sm text-foreground'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
export interface MarqueeStripProps {
|
|
4
|
+
/** Whether to continuously scroll the items. */
|
|
5
|
+
scroll?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface MarqueeStripAPI {
|
|
9
|
+
/** ARIA attributes to spread on the container element (`role="group"`). */
|
|
10
|
+
ariaProps: Partial<AccessibilityProps>
|
|
11
|
+
/** Data attributes for styling hooks. */
|
|
12
|
+
dataAttributes: Record<string, string>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build the framework-agnostic container props for a marquee strip.
|
|
17
|
+
*
|
|
18
|
+
* Returns `role="group"` plus a `data-scroll` attribute. Adapters spread these
|
|
19
|
+
* onto their container and render the label and items themselves. In scroll mode
|
|
20
|
+
* the adapter is responsible for duplicating items once to achieve a seamless
|
|
21
|
+
* loop (the CSS `marquee` keyframe translates from 0 to –50%).
|
|
22
|
+
*/
|
|
23
|
+
export function createMarqueeStrip(props: MarqueeStripProps = {}): MarqueeStripAPI {
|
|
24
|
+
const { scroll = false } = props
|
|
25
|
+
|
|
26
|
+
const ariaProps: Partial<AccessibilityProps> = {
|
|
27
|
+
role: 'group',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const dataAttributes: Record<string, string> = {
|
|
31
|
+
'data-scroll': String(scroll),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { ariaProps, dataAttributes }
|
|
35
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
clampPercent,
|
|
3
|
+
createMasteryBar,
|
|
4
|
+
type MasteryBarSize,
|
|
5
|
+
type MasteryBarProps,
|
|
6
|
+
type MasteryBarAPI,
|
|
7
|
+
} from './mastery-bar.js'
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
masteryBarVariants,
|
|
11
|
+
masteryBarFillVariants,
|
|
12
|
+
masteryBarLabelClass,
|
|
13
|
+
masteryBarLeadingLabelClass,
|
|
14
|
+
masteryBarHeaderClass,
|
|
15
|
+
} from './mastery-bar.styles.js'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Track (outer container): rounded, muted background.
|
|
5
|
+
* `size` variant controls the track height.
|
|
6
|
+
*/
|
|
7
|
+
export const masteryBarVariants = cva({
|
|
8
|
+
base: 'w-full overflow-hidden rounded-full bg-muted',
|
|
9
|
+
variants: {
|
|
10
|
+
size: {
|
|
11
|
+
sm: 'h-1.5',
|
|
12
|
+
md: 'h-2',
|
|
13
|
+
lg: 'h-3',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
size: 'md',
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Fill (inner bar): rounded, primary background.
|
|
23
|
+
* `muted` variant renders at reduced opacity for secondary/inactive states.
|
|
24
|
+
*/
|
|
25
|
+
export const masteryBarFillVariants = cva({
|
|
26
|
+
base: 'h-full rounded-full bg-primary',
|
|
27
|
+
variants: {
|
|
28
|
+
muted: {
|
|
29
|
+
true: 'bg-primary/70',
|
|
30
|
+
false: 'bg-primary',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
muted: 'false',
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
/** Class applied to each text label (leading or trailing). */
|
|
39
|
+
export const masteryBarLabelClass = 'text-xs text-muted-foreground'
|
|
40
|
+
|
|
41
|
+
/** Class for the leading (left-side) label. */
|
|
42
|
+
export const masteryBarLeadingLabelClass = 'text-xs text-muted-foreground'
|
|
43
|
+
|
|
44
|
+
/** Header row above the track: leading label on the left, label on the right. */
|
|
45
|
+
export const masteryBarHeaderClass = 'flex justify-between items-baseline mb-1'
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Visual size of a mastery bar. */
|
|
2
|
+
export type MasteryBarSize = 'sm' | 'md' | 'lg'
|
|
3
|
+
|
|
4
|
+
export interface MasteryBarProps {
|
|
5
|
+
/** Progress value (0–100). Values outside this range are clamped. */
|
|
6
|
+
value: number
|
|
7
|
+
/** Label shown on the right side of the header row. */
|
|
8
|
+
label?: string
|
|
9
|
+
/** Label shown on the left side of the header row. */
|
|
10
|
+
leadingLabel?: string
|
|
11
|
+
/** Visual size of the track. */
|
|
12
|
+
size?: MasteryBarSize
|
|
13
|
+
/** Renders the fill at reduced opacity to indicate a muted/secondary state. */
|
|
14
|
+
muted?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface MasteryBarAPI {
|
|
18
|
+
/** ARIA attributes to spread on the progressbar element. */
|
|
19
|
+
ariaProps: Record<string, string | number | boolean>
|
|
20
|
+
/** Data attributes for styling hooks. */
|
|
21
|
+
dataAttributes: Record<string, string>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Clamp a raw numeric value to the [0, 100] range.
|
|
26
|
+
*
|
|
27
|
+
* Values below 0 become 0; values above 100 become 100; NaN is treated as 0.
|
|
28
|
+
*/
|
|
29
|
+
export function clampPercent(value: number): number {
|
|
30
|
+
if (Number.isNaN(value)) return 0
|
|
31
|
+
return Math.min(100, Math.max(0, value))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Build the framework-agnostic progressbar props for a mastery bar.
|
|
36
|
+
*
|
|
37
|
+
* Returns `role="progressbar"` ARIA attributes plus data attributes; adapters
|
|
38
|
+
* spread these onto their track element.
|
|
39
|
+
*/
|
|
40
|
+
export function createMasteryBar({ value }: Pick<MasteryBarProps, 'value'>): MasteryBarAPI {
|
|
41
|
+
const clamped = clampPercent(value)
|
|
42
|
+
|
|
43
|
+
const ariaProps: Record<string, string | number | boolean> = {
|
|
44
|
+
role: 'progressbar',
|
|
45
|
+
'aria-valuenow': clamped,
|
|
46
|
+
'aria-valuemin': 0,
|
|
47
|
+
'aria-valuemax': 100,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const dataAttributes: Record<string, string> = {
|
|
51
|
+
'data-value': String(clamped),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { ariaProps, dataAttributes }
|
|
55
|
+
}
|
|
@@ -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'
|