@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,60 @@
|
|
|
1
|
+
/** The semantic kind of a terminal output line. */
|
|
2
|
+
export type TerminalLineKind = 'command' | 'stdout' | 'stderr' | 'info' | 'success'
|
|
3
|
+
|
|
4
|
+
/** A single line displayed in the terminal output panel. */
|
|
5
|
+
export interface TerminalLine {
|
|
6
|
+
/** Optional stable key for list rendering. */
|
|
7
|
+
id?: string
|
|
8
|
+
/** Semantic kind — drives styling and screen-reader announcements. */
|
|
9
|
+
kind: TerminalLineKind
|
|
10
|
+
/** Text content of the line. */
|
|
11
|
+
text: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface TerminalAPI {
|
|
15
|
+
/**
|
|
16
|
+
* ARIA attributes to spread on the terminal container element.
|
|
17
|
+
* - `role="log"` semantics: assistive technologies announce appended content.
|
|
18
|
+
* - `aria-live="polite"` so new output does not interrupt the user mid-action.
|
|
19
|
+
*/
|
|
20
|
+
ariaProps: {
|
|
21
|
+
role: 'log'
|
|
22
|
+
'aria-live': 'polite'
|
|
23
|
+
'aria-label'?: string
|
|
24
|
+
}
|
|
25
|
+
/** Data attributes for styling hooks. */
|
|
26
|
+
dataAttributes: Record<string, string>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build the prompt string shown before a command line.
|
|
31
|
+
*
|
|
32
|
+
* @param promptSymbol - The shell prompt character(s), e.g. `'$'` or `'❯'`.
|
|
33
|
+
* @param command - The command text that follows the prompt.
|
|
34
|
+
* @returns The formatted prompt string, e.g. `'$ python solution.py'`.
|
|
35
|
+
*/
|
|
36
|
+
export function formatPrompt(promptSymbol: string, command: string): string {
|
|
37
|
+
return `${promptSymbol} ${command}`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build the framework-agnostic ARIA and data props for a terminal output panel.
|
|
42
|
+
*
|
|
43
|
+
* Returns `role="log"` (live region semantics for streaming output) plus data
|
|
44
|
+
* attributes; adapters spread these onto their scroll container.
|
|
45
|
+
*/
|
|
46
|
+
export function createTerminal(options: { label?: string } = {}): TerminalAPI {
|
|
47
|
+
const ariaProps: TerminalAPI['ariaProps'] = {
|
|
48
|
+
role: 'log',
|
|
49
|
+
'aria-live': 'polite',
|
|
50
|
+
}
|
|
51
|
+
if (options.label) {
|
|
52
|
+
ariaProps['aria-label'] = options.label
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const dataAttributes: Record<string, string> = {
|
|
56
|
+
'data-component': 'terminal',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { ariaProps, dataAttributes }
|
|
60
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {
|
|
2
|
+
summarizeTests,
|
|
3
|
+
createTestResults,
|
|
4
|
+
type TestStatus,
|
|
5
|
+
type TestResultData,
|
|
6
|
+
type TestSummary,
|
|
7
|
+
type TestResultsAPI,
|
|
8
|
+
} from './test-results.js'
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
testResultsContainerVariants,
|
|
12
|
+
testResultsSummaryVariants,
|
|
13
|
+
testRowVariants,
|
|
14
|
+
testStatusBadgeVariants,
|
|
15
|
+
testNameClass,
|
|
16
|
+
testDurationClass,
|
|
17
|
+
testDiffBlockVariants,
|
|
18
|
+
testMessageClass,
|
|
19
|
+
} from './test-results.styles.js'
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/** Outer container that wraps the summary bar and the test row list. */
|
|
4
|
+
export const testResultsContainerVariants = cva({
|
|
5
|
+
base: 'flex flex-col gap-3 w-full',
|
|
6
|
+
variants: {},
|
|
7
|
+
defaultVariants: {},
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Summary bar ("2 / 3 passed").
|
|
12
|
+
*
|
|
13
|
+
* The `outcome` variant maps to a semantic color token so the bar turns green
|
|
14
|
+
* when everything passes, red when there are failures.
|
|
15
|
+
*/
|
|
16
|
+
export const testResultsSummaryVariants = cva({
|
|
17
|
+
base: [
|
|
18
|
+
'flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium',
|
|
19
|
+
'border',
|
|
20
|
+
].join(' '),
|
|
21
|
+
variants: {
|
|
22
|
+
outcome: {
|
|
23
|
+
pass: 'border-success/30 bg-success/10 text-success',
|
|
24
|
+
fail: 'border-destructive/30 bg-destructive/10 text-destructive',
|
|
25
|
+
skip: 'border-muted/30 bg-muted/10 text-muted-foreground',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: {
|
|
29
|
+
outcome: 'pass',
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
/** A single test row in the list. */
|
|
34
|
+
export const testRowVariants = cva({
|
|
35
|
+
base: [
|
|
36
|
+
'flex flex-col gap-1.5 rounded-md border px-3 py-2.5 text-sm',
|
|
37
|
+
].join(' '),
|
|
38
|
+
variants: {
|
|
39
|
+
status: {
|
|
40
|
+
pass: 'border-success/20 bg-success/5',
|
|
41
|
+
fail: 'border-destructive/20 bg-destructive/5',
|
|
42
|
+
skip: 'border-muted/20 bg-muted/5',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
defaultVariants: {
|
|
46
|
+
status: 'pass',
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
/** Status icon / badge inside a test row (the PASS / FAIL / SKIP pill). */
|
|
51
|
+
export const testStatusBadgeVariants = cva({
|
|
52
|
+
base: 'inline-flex items-center rounded px-1.5 py-0.5 text-xs font-semibold uppercase tracking-wide',
|
|
53
|
+
variants: {
|
|
54
|
+
status: {
|
|
55
|
+
pass: 'bg-success/20 text-success',
|
|
56
|
+
fail: 'bg-destructive/20 text-destructive',
|
|
57
|
+
skip: 'bg-muted text-muted-foreground',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
defaultVariants: {
|
|
61
|
+
status: 'pass',
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
/** Test name text inside a row. */
|
|
66
|
+
export const testNameClass = 'font-medium text-foreground'
|
|
67
|
+
|
|
68
|
+
/** Duration text ("12 ms") inside a row. */
|
|
69
|
+
export const testDurationClass = 'text-xs text-muted-foreground'
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Expected / actual diff block shown beneath a failing test row.
|
|
73
|
+
*
|
|
74
|
+
* The `side` variant sets the left-border accent color to distinguish the two
|
|
75
|
+
* sides of the diff.
|
|
76
|
+
*/
|
|
77
|
+
export const testDiffBlockVariants = cva({
|
|
78
|
+
base: [
|
|
79
|
+
'rounded border-l-2 bg-muted/30 px-3 py-2 font-mono text-xs text-foreground',
|
|
80
|
+
].join(' '),
|
|
81
|
+
variants: {
|
|
82
|
+
side: {
|
|
83
|
+
expected: 'border-l-success text-success',
|
|
84
|
+
actual: 'border-l-destructive text-destructive',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
defaultVariants: {
|
|
88
|
+
side: 'actual',
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
/** Error / skip message text beneath a test row. */
|
|
93
|
+
export const testMessageClass = 'text-xs text-muted-foreground italic'
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/** The execution status of a single test case. */
|
|
4
|
+
export type TestStatus = 'pass' | 'fail' | 'skip'
|
|
5
|
+
|
|
6
|
+
/** Data for a single test case result. */
|
|
7
|
+
export interface TestResultData {
|
|
8
|
+
/** Unique identifier for the test. */
|
|
9
|
+
id: string
|
|
10
|
+
/** Human-readable test name. */
|
|
11
|
+
name: string
|
|
12
|
+
/** Execution status. */
|
|
13
|
+
status: TestStatus
|
|
14
|
+
/** How long the test took to run, in milliseconds. */
|
|
15
|
+
durationMs?: number
|
|
16
|
+
/** Expected value (shown in diff when the test fails). */
|
|
17
|
+
expected?: string
|
|
18
|
+
/** Actual value received (shown in diff when the test fails). */
|
|
19
|
+
actual?: string
|
|
20
|
+
/** Optional message — error message on failure, skip reason, etc. */
|
|
21
|
+
message?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Aggregate counts derived from a list of test results. */
|
|
25
|
+
export interface TestSummary {
|
|
26
|
+
total: number
|
|
27
|
+
passed: number
|
|
28
|
+
failed: number
|
|
29
|
+
skipped: number
|
|
30
|
+
/** True when every test passed (failed === 0 and skipped === 0). */
|
|
31
|
+
allPassed: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TestResultsAPI {
|
|
35
|
+
/** ARIA attributes to spread on the list container (`role="list"`). */
|
|
36
|
+
ariaProps: Partial<AccessibilityProps>
|
|
37
|
+
/** Data attributes for styling hooks. */
|
|
38
|
+
dataAttributes: Record<string, string>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Compute aggregate summary counts from a list of {@link TestResultData}.
|
|
43
|
+
*
|
|
44
|
+
* Pure function — no side effects, safe to call during SSR.
|
|
45
|
+
*/
|
|
46
|
+
export function summarizeTests(results: TestResultData[]): TestSummary {
|
|
47
|
+
let passed = 0
|
|
48
|
+
let failed = 0
|
|
49
|
+
let skipped = 0
|
|
50
|
+
|
|
51
|
+
for (const r of results) {
|
|
52
|
+
if (r.status === 'pass') passed++
|
|
53
|
+
else if (r.status === 'fail') failed++
|
|
54
|
+
else skipped++
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
total: results.length,
|
|
59
|
+
passed,
|
|
60
|
+
failed,
|
|
61
|
+
skipped,
|
|
62
|
+
allPassed: failed === 0 && skipped === 0 && results.length > 0,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build the framework-agnostic container props for a test results list.
|
|
68
|
+
*
|
|
69
|
+
* Returns `role="list"` plus data attributes that adapters spread onto their
|
|
70
|
+
* container element. The adapter is responsible for rendering individual items
|
|
71
|
+
* with `role="listitem"`.
|
|
72
|
+
*/
|
|
73
|
+
export function createTestResults(): TestResultsAPI {
|
|
74
|
+
const ariaProps: Partial<AccessibilityProps> = {
|
|
75
|
+
role: 'list',
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const dataAttributes: Record<string, string> = {
|
|
79
|
+
'data-component': 'test-results',
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { ariaProps, dataAttributes }
|
|
83
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createTimeline,
|
|
3
|
+
getTimelineItemAria,
|
|
4
|
+
resolveTimelineItems,
|
|
5
|
+
type TimelineOrientation,
|
|
6
|
+
type TimelineItemStatus,
|
|
7
|
+
type TimelineItemData,
|
|
8
|
+
type TimelineProps,
|
|
9
|
+
type TimelineAPI,
|
|
10
|
+
} from './timeline.js'
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
timelineVariants,
|
|
14
|
+
timelineItemVariants,
|
|
15
|
+
timelineMarkerVariants,
|
|
16
|
+
timelineConnectorVariants,
|
|
17
|
+
timelineGutterClass,
|
|
18
|
+
timelineTimeClass,
|
|
19
|
+
timelineTitleClass,
|
|
20
|
+
timelineDescriptionClass,
|
|
21
|
+
timelineContentClass,
|
|
22
|
+
} from './timeline.styles.js'
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Outer rail / list container.
|
|
5
|
+
*
|
|
6
|
+
* Vertical: a single column; horizontal: a row so items sit side-by-side.
|
|
7
|
+
*/
|
|
8
|
+
export const timelineVariants = cva({
|
|
9
|
+
base: 'relative',
|
|
10
|
+
variants: {
|
|
11
|
+
orientation: {
|
|
12
|
+
vertical: 'flex flex-col',
|
|
13
|
+
horizontal: 'flex flex-row items-start overflow-x-auto',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
orientation: 'vertical',
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A single event row / column.
|
|
23
|
+
*
|
|
24
|
+
* In vertical mode items stack with a left gutter for the marker + connector.
|
|
25
|
+
* In horizontal mode each item takes equal width and the connector runs above.
|
|
26
|
+
*/
|
|
27
|
+
export const timelineItemVariants = cva({
|
|
28
|
+
base: 'relative flex gap-4',
|
|
29
|
+
variants: {
|
|
30
|
+
orientation: {
|
|
31
|
+
vertical: 'flex-row pb-8 last:pb-0',
|
|
32
|
+
horizontal: 'flex-col items-center flex-1 px-2',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
orientation: 'vertical',
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The marker / dot that represents the event on the rail.
|
|
42
|
+
*
|
|
43
|
+
* Status variants map directly to semantic token colours so theming is
|
|
44
|
+
* handled at the design-token level, not inline.
|
|
45
|
+
*/
|
|
46
|
+
export const timelineMarkerVariants = cva({
|
|
47
|
+
base: [
|
|
48
|
+
'relative z-10 flex shrink-0 items-center justify-center',
|
|
49
|
+
'size-3 rounded-full border-2',
|
|
50
|
+
'transition-colors',
|
|
51
|
+
].join(' '),
|
|
52
|
+
variants: {
|
|
53
|
+
status: {
|
|
54
|
+
done: 'bg-primary border-primary',
|
|
55
|
+
current: 'bg-background border-primary ring-2 ring-primary ring-offset-2 ring-offset-background',
|
|
56
|
+
upcoming: 'bg-muted border-muted-foreground/30',
|
|
57
|
+
default: 'bg-background border-border',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
defaultVariants: {
|
|
61
|
+
status: 'default',
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The connector line drawn between adjacent markers.
|
|
67
|
+
*
|
|
68
|
+
* Vertical: a thin vertical stripe anchored to the left gutter.
|
|
69
|
+
* Horizontal: a thin horizontal stripe anchored above the content row.
|
|
70
|
+
* The last item hides its connector so the rail terminates cleanly.
|
|
71
|
+
*/
|
|
72
|
+
export const timelineConnectorVariants = cva({
|
|
73
|
+
base: 'absolute bg-border',
|
|
74
|
+
variants: {
|
|
75
|
+
orientation: {
|
|
76
|
+
vertical: 'left-[5px] top-3 w-0.5 h-full',
|
|
77
|
+
horizontal: 'top-[5px] left-1/2 w-full h-0.5',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
defaultVariants: {
|
|
81
|
+
orientation: 'vertical',
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
/** Wrapper that holds the marker + connector in the gutter column/row. */
|
|
86
|
+
export const timelineGutterClass = 'relative flex flex-col items-center'
|
|
87
|
+
|
|
88
|
+
/** Time / date label rendered near the marker. */
|
|
89
|
+
export const timelineTimeClass = 'text-xs text-muted-foreground tabular-nums'
|
|
90
|
+
|
|
91
|
+
/** Primary event title. */
|
|
92
|
+
export const timelineTitleClass = 'text-sm font-medium text-foreground leading-snug'
|
|
93
|
+
|
|
94
|
+
/** Secondary description text. */
|
|
95
|
+
export const timelineDescriptionClass = 'mt-0.5 text-xs text-muted-foreground leading-relaxed'
|
|
96
|
+
|
|
97
|
+
/** Content block (title + description) next to or below the marker. */
|
|
98
|
+
export const timelineContentClass = 'flex flex-col min-w-0'
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/** Layout orientation of the timeline. */
|
|
4
|
+
export type TimelineOrientation = 'vertical' | 'horizontal'
|
|
5
|
+
|
|
6
|
+
/** Visual status of a single timeline item. */
|
|
7
|
+
export type TimelineItemStatus = 'done' | 'current' | 'upcoming' | 'default'
|
|
8
|
+
|
|
9
|
+
/** Data describing a single event in the timeline. */
|
|
10
|
+
export interface TimelineItemData {
|
|
11
|
+
/** Unique identifier for the item. */
|
|
12
|
+
id: string
|
|
13
|
+
/** Primary label shown beside the marker. */
|
|
14
|
+
title: string
|
|
15
|
+
/** Optional timestamp / date string shown near the marker. */
|
|
16
|
+
time?: string
|
|
17
|
+
/** Additional detail text rendered below the title. */
|
|
18
|
+
description?: string
|
|
19
|
+
/** Visual status of this item. Defaults to `'default'` when omitted. */
|
|
20
|
+
status?: TimelineItemStatus
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TimelineProps {
|
|
24
|
+
/** Ordered list of events to display. */
|
|
25
|
+
items: TimelineItemData[]
|
|
26
|
+
/** Layout direction. */
|
|
27
|
+
orientation?: TimelineOrientation
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface TimelineAPI {
|
|
31
|
+
/** ARIA attributes to spread on the list container (`role="list"`). */
|
|
32
|
+
ariaProps: Partial<AccessibilityProps>
|
|
33
|
+
/** Data attributes for styling hooks. */
|
|
34
|
+
dataAttributes: Record<string, string>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build the framework-agnostic container props for a timeline.
|
|
39
|
+
*
|
|
40
|
+
* Returns `role="list"` (the items carry `role="listitem"`) plus a
|
|
41
|
+
* `data-orientation` attribute. The function is JSX-free so it can run in
|
|
42
|
+
* any environment including server-side Node and Astro SSR.
|
|
43
|
+
*/
|
|
44
|
+
export function createTimeline(
|
|
45
|
+
props: Pick<TimelineProps, 'orientation'> = {},
|
|
46
|
+
): TimelineAPI {
|
|
47
|
+
const { orientation = 'vertical' } = props
|
|
48
|
+
|
|
49
|
+
const ariaProps: Partial<AccessibilityProps> = {
|
|
50
|
+
role: 'list',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const dataAttributes: Record<string, string> = {
|
|
54
|
+
'data-orientation': orientation,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { ariaProps, dataAttributes }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Accessibility props for a single timeline item.
|
|
62
|
+
*
|
|
63
|
+
* Every item gets `role="listitem"` regardless of status so screen readers
|
|
64
|
+
* announce the full list correctly.
|
|
65
|
+
*/
|
|
66
|
+
export function getTimelineItemAria(): Partial<AccessibilityProps> {
|
|
67
|
+
return { role: 'listitem' }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Normalise a raw list of timeline items so every entry has an explicit
|
|
72
|
+
* `status`. Items without a status receive `'default'`.
|
|
73
|
+
*
|
|
74
|
+
* This is the single source of truth shared by every adapter — call it once
|
|
75
|
+
* at render time and iterate over the result.
|
|
76
|
+
*/
|
|
77
|
+
export function resolveTimelineItems(
|
|
78
|
+
items: TimelineItemData[],
|
|
79
|
+
): Required<TimelineItemData>[] {
|
|
80
|
+
return items.map((item) => ({
|
|
81
|
+
...item,
|
|
82
|
+
status: item.status ?? 'default',
|
|
83
|
+
time: item.time ?? '',
|
|
84
|
+
description: item.description ?? '',
|
|
85
|
+
}))
|
|
86
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createVideoGrid,
|
|
3
|
+
computeGridColumns,
|
|
4
|
+
bucketByCount,
|
|
5
|
+
type VideoGridLayout,
|
|
6
|
+
type VideoGridBucket,
|
|
7
|
+
type VideoGridHeadlessProps,
|
|
8
|
+
type VideoGridAPI,
|
|
9
|
+
} from './video-grid.js'
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
videoGridVariants,
|
|
13
|
+
videoGridSpotlightClass,
|
|
14
|
+
videoGridFilmstripClass,
|
|
15
|
+
videoGridFilmstripTileClass,
|
|
16
|
+
} from './video-grid.styles.js'
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Grid container variants.
|
|
5
|
+
*
|
|
6
|
+
* - `layout: 'grid'` — uniform CSS grid; column count is set inline by the
|
|
7
|
+
* adapter using `computeGridColumns` (gridTemplateColumns).
|
|
8
|
+
* - `layout: 'speaker'` — two-row layout: large spotlight on top, filmstrip row
|
|
9
|
+
* below. The adapter renders the spotlight as the first child and a horizontal
|
|
10
|
+
* overflow row for remaining tiles.
|
|
11
|
+
*/
|
|
12
|
+
export const videoGridVariants = cva({
|
|
13
|
+
base: 'w-full h-full gap-2',
|
|
14
|
+
variants: {
|
|
15
|
+
layout: {
|
|
16
|
+
grid: 'grid',
|
|
17
|
+
speaker: 'flex flex-col',
|
|
18
|
+
auto: 'grid',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
layout: 'auto',
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
/** Full-bleed spotlight area in speaker view (takes up remaining vertical space). */
|
|
27
|
+
export const videoGridSpotlightClass = 'relative flex-1 min-h-0 rounded-xl overflow-hidden bg-muted'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Horizontal filmstrip row used in speaker view.
|
|
31
|
+
* Each tile in this row is constrained to a fixed width so the row scrolls
|
|
32
|
+
* rather than wraps on large calls.
|
|
33
|
+
*/
|
|
34
|
+
export const videoGridFilmstripClass =
|
|
35
|
+
'flex flex-row gap-2 overflow-x-auto py-1 shrink-0'
|
|
36
|
+
|
|
37
|
+
/** Individual filmstrip tile wrapper — fixed width so they don't collapse. */
|
|
38
|
+
export const videoGridFilmstripTileClass = 'w-36 shrink-0'
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout mode for a VideoGrid.
|
|
3
|
+
*
|
|
4
|
+
* - 'auto' — choose grid or speaker view based on participant count (≤1 forces speaker)
|
|
5
|
+
* - 'grid' — always render an even CSS grid of tiles
|
|
6
|
+
* - 'speaker' — render one large spotlight tile + a filmstrip row for the rest
|
|
7
|
+
*/
|
|
8
|
+
export type VideoGridLayout = 'auto' | 'grid' | 'speaker'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Bucket label that describes the rough meeting size.
|
|
12
|
+
* Useful for analytics and adaptive UI decisions.
|
|
13
|
+
*/
|
|
14
|
+
export type VideoGridBucket =
|
|
15
|
+
| 'solo'
|
|
16
|
+
| 'pair'
|
|
17
|
+
| 'small'
|
|
18
|
+
| 'medium'
|
|
19
|
+
| 'large'
|
|
20
|
+
| 'townhall'
|
|
21
|
+
|
|
22
|
+
export interface VideoGridHeadlessProps {
|
|
23
|
+
/** Desired layout mode. */
|
|
24
|
+
layout?: VideoGridLayout
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface VideoGridAPI {
|
|
28
|
+
/** ARIA attributes to spread on the grid container. */
|
|
29
|
+
ariaProps: {
|
|
30
|
+
role: 'group'
|
|
31
|
+
}
|
|
32
|
+
/** Data attributes for CSS styling hooks. */
|
|
33
|
+
dataAttributes: {
|
|
34
|
+
'data-layout': VideoGridLayout
|
|
35
|
+
'data-count'?: string
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Compute the number of CSS grid columns for N participants.
|
|
41
|
+
*
|
|
42
|
+
* The responsive rule mirrors easyloops meeting layouts:
|
|
43
|
+
*
|
|
44
|
+
* | count | columns |
|
|
45
|
+
* |-------|---------|
|
|
46
|
+
* | 1 | 1 |
|
|
47
|
+
* | 2 | 2 |
|
|
48
|
+
* | 3–4 | 2 |
|
|
49
|
+
* | 5–9 | 3 |
|
|
50
|
+
* | 10–16 | 4 |
|
|
51
|
+
* | 17–25 | 5 |
|
|
52
|
+
* | 26+ | 6 |
|
|
53
|
+
*/
|
|
54
|
+
export function computeGridColumns(count: number): number {
|
|
55
|
+
if (count <= 1) return 1
|
|
56
|
+
if (count <= 4) return 2
|
|
57
|
+
if (count <= 9) return 3
|
|
58
|
+
if (count <= 16) return 4
|
|
59
|
+
if (count <= 25) return 5
|
|
60
|
+
return 6
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Label the meeting size as a human-readable bucket.
|
|
65
|
+
*
|
|
66
|
+
* Useful for analytics events and adaptive UI decisions without
|
|
67
|
+
* coupling to the exact column math.
|
|
68
|
+
*/
|
|
69
|
+
export function bucketByCount(count: number): VideoGridBucket {
|
|
70
|
+
if (count <= 1) return 'solo'
|
|
71
|
+
if (count <= 2) return 'pair'
|
|
72
|
+
if (count <= 9) return 'small'
|
|
73
|
+
if (count <= 16) return 'medium'
|
|
74
|
+
if (count <= 25) return 'large'
|
|
75
|
+
return 'townhall'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Build the framework-agnostic ARIA and data attributes for a VideoGrid
|
|
80
|
+
* container. Adapters spread these onto their host element.
|
|
81
|
+
*/
|
|
82
|
+
export function createVideoGrid(props: VideoGridHeadlessProps = {}): VideoGridAPI {
|
|
83
|
+
const { layout = 'auto' } = props
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
ariaProps: {
|
|
87
|
+
role: 'group',
|
|
88
|
+
},
|
|
89
|
+
dataAttributes: {
|
|
90
|
+
'data-layout': layout,
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createVideoTile,
|
|
3
|
+
getInitials,
|
|
4
|
+
type VideoTileMicState,
|
|
5
|
+
type VideoTileData,
|
|
6
|
+
type VideoTileHeadlessProps,
|
|
7
|
+
type VideoTileAPI,
|
|
8
|
+
} from './video-tile.js'
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
videoTileVariants,
|
|
12
|
+
videoTileNameChipClass,
|
|
13
|
+
videoTileMicIconClass,
|
|
14
|
+
videoTileAvatarFallbackClass,
|
|
15
|
+
videoTileAvatarCircleClass,
|
|
16
|
+
videoTileReactionBadgeClass,
|
|
17
|
+
} from './video-tile.styles.js'
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Root tile container — aspect-video, rounded corners, clipped media.
|
|
5
|
+
*
|
|
6
|
+
* `speaking` variant adds a visible ring to indicate active audio.
|
|
7
|
+
* `pinned` variant adds a stronger ring to mark a pinned participant.
|
|
8
|
+
*
|
|
9
|
+
* cva boolean-like variants use string values ('true'/'false') per the
|
|
10
|
+
* refraction-ui convention — callers must pass strings, not raw booleans.
|
|
11
|
+
*/
|
|
12
|
+
export const videoTileVariants = cva({
|
|
13
|
+
base: [
|
|
14
|
+
'relative aspect-video overflow-hidden rounded-lg bg-muted',
|
|
15
|
+
'transition-shadow duration-200',
|
|
16
|
+
].join(' '),
|
|
17
|
+
variants: {
|
|
18
|
+
speaking: {
|
|
19
|
+
'true': 'ring-2 ring-emerald-500 dark:ring-emerald-400 ring-offset-2 ring-offset-background',
|
|
20
|
+
'false': '',
|
|
21
|
+
},
|
|
22
|
+
pinned: {
|
|
23
|
+
'true': 'ring-2 ring-primary ring-offset-2 ring-offset-background',
|
|
24
|
+
'false': '',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
speaking: 'false',
|
|
29
|
+
pinned: 'false',
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Name chip anchored to the bottom-left of the tile.
|
|
35
|
+
* Semi-transparent bg keeps it legible over any media content.
|
|
36
|
+
*/
|
|
37
|
+
export const videoTileNameChipClass =
|
|
38
|
+
'absolute bottom-2 left-2 flex items-center gap-1.5 rounded-md bg-background/70 px-2 py-0.5 text-xs font-medium text-foreground backdrop-blur-sm'
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Mic-muted icon container — sits inside the name chip at the trailing edge.
|
|
42
|
+
*/
|
|
43
|
+
export const videoTileMicIconClass = 'text-destructive'
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Avatar fallback circle — centred in the tile when no media stream is available.
|
|
47
|
+
*/
|
|
48
|
+
export const videoTileAvatarFallbackClass =
|
|
49
|
+
'absolute inset-0 flex items-center justify-center'
|
|
50
|
+
|
|
51
|
+
/** Inner circle with initials. */
|
|
52
|
+
export const videoTileAvatarCircleClass =
|
|
53
|
+
'flex size-16 items-center justify-center rounded-full bg-card text-foreground text-xl font-semibold select-none'
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Reaction badge slot — anchored to the top-right corner.
|
|
57
|
+
*/
|
|
58
|
+
export const videoTileReactionBadgeClass =
|
|
59
|
+
'absolute right-2 top-2 flex items-center justify-center rounded-full bg-background/70 px-1.5 py-0.5 text-sm backdrop-blur-sm'
|