@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.
Files changed (148) hide show
  1. package/dist/astro-audio-room/AudioRoom.astro +81 -0
  2. package/dist/astro-audio-room/index.ts +1 -0
  3. package/dist/astro-call-controls/CallControls.astro +57 -0
  4. package/dist/astro-call-controls/index.ts +1 -0
  5. package/dist/astro-checklist/Checklist.astro +76 -0
  6. package/dist/astro-checklist/index.ts +1 -0
  7. package/dist/astro-editor-status-bar/EditorStatusBar.astro +92 -0
  8. package/dist/astro-editor-status-bar/index.ts +1 -0
  9. package/dist/astro-editor-tabs/EditorTabs.astro +74 -0
  10. package/dist/astro-editor-tabs/index.ts +1 -0
  11. package/dist/astro-empty-state/EmptyState.astro +57 -0
  12. package/dist/astro-empty-state/index.ts +1 -0
  13. package/dist/astro-floating-reactions/FloatingReactions.astro +56 -0
  14. package/dist/astro-floating-reactions/index.ts +1 -0
  15. package/dist/astro-flow-editor/FlowEditor.astro +99 -0
  16. package/dist/astro-flow-editor/index.ts +3 -0
  17. package/dist/astro-graph-view/GraphView.astro +109 -0
  18. package/dist/astro-graph-view/index.ts +1 -0
  19. package/dist/astro-infinite-canvas/InfiniteCanvas.astro +43 -0
  20. package/dist/astro-infinite-canvas/index.ts +1 -0
  21. package/dist/astro-input/Input.astro +5 -1
  22. package/dist/astro-live-captions/LiveCaptions.astro +54 -0
  23. package/dist/astro-live-captions/index.ts +1 -0
  24. package/dist/astro-live-cursors/LiveCursors.astro +55 -0
  25. package/dist/astro-live-cursors/index.ts +1 -0
  26. package/dist/astro-live-transcript/LiveTranscript.astro +55 -0
  27. package/dist/astro-live-transcript/index.ts +1 -0
  28. package/dist/astro-mini-map/MiniMap.astro +74 -0
  29. package/dist/astro-mini-map/index.ts +1 -0
  30. package/dist/astro-password-input/PasswordInput.astro +102 -0
  31. package/dist/astro-password-input/index.ts +1 -0
  32. package/dist/astro-pre-call-lobby/PreCallLobby.astro +202 -0
  33. package/dist/astro-pre-call-lobby/index.ts +1 -0
  34. package/dist/astro-radial-gauge/RadialGauge.astro +144 -0
  35. package/dist/astro-radial-gauge/index.ts +1 -0
  36. package/dist/astro-rating-scale/RatingScale.astro +73 -0
  37. package/dist/astro-rating-scale/index.ts +1 -0
  38. package/dist/astro-segmented-control/SegmentedControl.astro +106 -0
  39. package/dist/astro-segmented-control/SegmentedControlItem.astro +32 -0
  40. package/dist/astro-segmented-control/index.ts +1 -0
  41. package/dist/astro-separator/Separator.astro +49 -0
  42. package/dist/astro-separator/index.ts +1 -0
  43. package/dist/astro-social-auth-button/SocialAuthButton.astro +110 -0
  44. package/dist/astro-social-auth-button/SocialAuthRow.astro +13 -0
  45. package/dist/astro-social-auth-button/index.ts +1 -0
  46. package/dist/astro-sticky-note/StickyNote.astro +55 -0
  47. package/dist/astro-sticky-note/index.ts +1 -0
  48. package/dist/astro-terminal/Terminal.astro +67 -0
  49. package/dist/astro-terminal/index.ts +1 -0
  50. package/dist/astro-test-results/TestResults.astro +76 -0
  51. package/dist/astro-test-results/index.ts +1 -0
  52. package/dist/astro-timeline/Timeline.astro +59 -0
  53. package/dist/astro-timeline/index.ts +1 -0
  54. package/dist/astro-video-grid/VideoGrid.astro +182 -0
  55. package/dist/astro-video-grid/index.ts +1 -0
  56. package/dist/astro-video-tile/VideoTile.astro +104 -0
  57. package/dist/astro-video-tile/index.ts +1 -0
  58. package/dist/astro-wizard/Wizard.astro +137 -0
  59. package/dist/astro-wizard/index.ts +1 -0
  60. package/dist/audio-room/audio-room.styles.ts +88 -0
  61. package/dist/audio-room/audio-room.ts +70 -0
  62. package/dist/audio-room/index.ts +16 -0
  63. package/dist/call-controls/call-controls.styles.ts +63 -0
  64. package/dist/call-controls/call-controls.ts +79 -0
  65. package/dist/call-controls/index.ts +15 -0
  66. package/dist/checklist/checklist.styles.ts +76 -0
  67. package/dist/checklist/checklist.ts +62 -0
  68. package/dist/checklist/index.ts +18 -0
  69. package/dist/editor-status-bar/editor-status-bar.styles.ts +40 -0
  70. package/dist/editor-status-bar/editor-status-bar.ts +78 -0
  71. package/dist/editor-status-bar/index.ts +15 -0
  72. package/dist/editor-tabs/editor-tabs.styles.ts +52 -0
  73. package/dist/editor-tabs/editor-tabs.ts +82 -0
  74. package/dist/editor-tabs/index.ts +15 -0
  75. package/dist/empty-state/empty-state.styles.ts +42 -0
  76. package/dist/empty-state/empty-state.ts +27 -0
  77. package/dist/empty-state/index.ts +14 -0
  78. package/dist/floating-reactions/floating-reactions.styles.ts +54 -0
  79. package/dist/floating-reactions/floating-reactions.ts +85 -0
  80. package/dist/floating-reactions/index.ts +14 -0
  81. package/dist/flow-editor/flow-editor.styles.ts +56 -0
  82. package/dist/flow-editor/flow-editor.ts +126 -0
  83. package/dist/flow-editor/index.ts +18 -0
  84. package/dist/graph-view/graph-view.styles.ts +66 -0
  85. package/dist/graph-view/graph-view.ts +130 -0
  86. package/dist/graph-view/index.ts +21 -0
  87. package/dist/index.ts +45 -17
  88. package/dist/infinite-canvas/index.ts +17 -0
  89. package/dist/infinite-canvas/infinite-canvas.styles.ts +49 -0
  90. package/dist/infinite-canvas/infinite-canvas.ts +97 -0
  91. package/dist/input/index.ts +2 -0
  92. package/dist/input/input.styles.ts +4 -0
  93. package/dist/input/input.ts +39 -3
  94. package/dist/live-captions/index.ts +13 -0
  95. package/dist/live-captions/live-captions.styles.ts +45 -0
  96. package/dist/live-captions/live-captions.ts +57 -0
  97. package/dist/live-cursors/index.ts +14 -0
  98. package/dist/live-cursors/live-cursors.styles.ts +41 -0
  99. package/dist/live-cursors/live-cursors.ts +73 -0
  100. package/dist/live-transcript/index.ts +16 -0
  101. package/dist/live-transcript/live-transcript.styles.ts +48 -0
  102. package/dist/live-transcript/live-transcript.ts +82 -0
  103. package/dist/mini-map/index.ts +16 -0
  104. package/dist/mini-map/mini-map.styles.ts +32 -0
  105. package/dist/mini-map/mini-map.ts +122 -0
  106. package/dist/password-input/index.ts +9 -0
  107. package/dist/password-input/password-input.styles.ts +10 -0
  108. package/dist/password-input/password-input.ts +45 -0
  109. package/dist/pre-call-lobby/index.ts +22 -0
  110. package/dist/pre-call-lobby/pre-call-lobby.styles.ts +108 -0
  111. package/dist/pre-call-lobby/pre-call-lobby.ts +61 -0
  112. package/dist/radial-gauge/index.ts +26 -0
  113. package/dist/radial-gauge/radial-gauge.styles.ts +75 -0
  114. package/dist/radial-gauge/radial-gauge.ts +183 -0
  115. package/dist/rating-scale/index.ts +16 -0
  116. package/dist/rating-scale/rating-scale.styles.ts +48 -0
  117. package/dist/rating-scale/rating-scale.ts +101 -0
  118. package/dist/segmented-control/index.ts +13 -0
  119. package/dist/segmented-control/segmented-control.styles.ts +42 -0
  120. package/dist/segmented-control/segmented-control.ts +80 -0
  121. package/dist/separator/index.ts +12 -0
  122. package/dist/separator/separator.styles.ts +34 -0
  123. package/dist/separator/separator.ts +53 -0
  124. package/dist/social-auth-button/index.ts +14 -0
  125. package/dist/social-auth-button/social-auth-button.styles.ts +16 -0
  126. package/dist/social-auth-button/social-auth-button.ts +51 -0
  127. package/dist/sticky-note/index.ts +14 -0
  128. package/dist/sticky-note/sticky-note.styles.ts +37 -0
  129. package/dist/sticky-note/sticky-note.ts +47 -0
  130. package/dist/terminal/index.ts +13 -0
  131. package/dist/terminal/terminal.styles.ts +56 -0
  132. package/dist/terminal/terminal.ts +60 -0
  133. package/dist/test-results/index.ts +19 -0
  134. package/dist/test-results/test-results.styles.ts +93 -0
  135. package/dist/test-results/test-results.ts +83 -0
  136. package/dist/timeline/index.ts +22 -0
  137. package/dist/timeline/timeline.styles.ts +98 -0
  138. package/dist/timeline/timeline.ts +86 -0
  139. package/dist/video-grid/index.ts +16 -0
  140. package/dist/video-grid/video-grid.styles.ts +38 -0
  141. package/dist/video-grid/video-grid.ts +93 -0
  142. package/dist/video-tile/index.ts +17 -0
  143. package/dist/video-tile/video-tile.styles.ts +59 -0
  144. package/dist/video-tile/video-tile.ts +74 -0
  145. package/dist/wizard/index.ts +28 -0
  146. package/dist/wizard/wizard.styles.ts +147 -0
  147. package/dist/wizard/wizard.ts +128 -0
  148. package/package.json +47 -19
@@ -0,0 +1,80 @@
1
+ import type { AccessibilityProps } from '../shared/index.ts'
2
+
3
+ export type SegmentedControlSize = 'sm' | 'md'
4
+
5
+ /** Value identifying a single segment. */
6
+ export type SegmentedControlValue = string
7
+
8
+ export interface SegmentedControlProps {
9
+ /** Currently selected segment value (controlled). */
10
+ value?: SegmentedControlValue
11
+ /** Visual size of the control. */
12
+ size?: SegmentedControlSize
13
+ }
14
+
15
+ export interface SegmentedControlAPI {
16
+ /** ARIA attributes to spread on the group element. */
17
+ ariaProps: Partial<AccessibilityProps>
18
+ /** Data attributes for CSS styling hooks. */
19
+ dataAttributes: Record<string, string>
20
+ }
21
+
22
+ /**
23
+ * Build the framework-agnostic group props for a segmented control.
24
+ *
25
+ * Returns `role="radiogroup"` plus data attributes; adapters spread these
26
+ * onto their group element and handle their own rendering/state.
27
+ */
28
+ export function createSegmentedControl(
29
+ props: SegmentedControlProps = {},
30
+ ): SegmentedControlAPI {
31
+ const { value, size = 'md' } = props
32
+
33
+ const ariaProps: Partial<AccessibilityProps> = {
34
+ role: 'radiogroup',
35
+ }
36
+
37
+ const dataAttributes: Record<string, string> = {
38
+ 'data-size': size,
39
+ }
40
+ if (value !== undefined) {
41
+ dataAttributes['data-value'] = value
42
+ }
43
+
44
+ return { ariaProps, dataAttributes }
45
+ }
46
+
47
+ /**
48
+ * Pure keyboard-navigation rule shared by every adapter.
49
+ *
50
+ * Given the current index, the pressed key, and the item count, returns the
51
+ * index that focus/selection should move to. Arrow keys wrap; Home/End jump
52
+ * to the ends. Unhandled keys (or an empty group) return `current` unchanged.
53
+ *
54
+ * - ArrowRight / ArrowDown → next (wraps to 0 past the end)
55
+ * - ArrowLeft / ArrowUp → previous (wraps to last before 0)
56
+ * - Home → first
57
+ * - End → last
58
+ */
59
+ export function getNextSegmentIndex(
60
+ current: number,
61
+ key: string,
62
+ count: number,
63
+ ): number {
64
+ if (count <= 0) return current
65
+
66
+ switch (key) {
67
+ case 'ArrowRight':
68
+ case 'ArrowDown':
69
+ return (current + 1) % count
70
+ case 'ArrowLeft':
71
+ case 'ArrowUp':
72
+ return (current - 1 + count) % count
73
+ case 'Home':
74
+ return 0
75
+ case 'End':
76
+ return count - 1
77
+ default:
78
+ return current
79
+ }
80
+ }
@@ -0,0 +1,12 @@
1
+ export {
2
+ createSeparator,
3
+ type SeparatorOrientation,
4
+ type SeparatorProps,
5
+ type SeparatorAPI,
6
+ } from './separator.js'
7
+
8
+ export {
9
+ separatorVariants,
10
+ separatorLineClass,
11
+ separatorLabelClass,
12
+ } from './separator.styles.js'
@@ -0,0 +1,34 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * Visual variants for the separator rule.
5
+ *
6
+ * - `orientation: horizontal` → a full-width 1px line.
7
+ * - `orientation: vertical` → a full-height 1px line.
8
+ * - `labeled` → flex layout used by the labeled-divider variant, which
9
+ * arranges a centered label between two flanking lines.
10
+ */
11
+ export const separatorVariants = cva({
12
+ base: '',
13
+ variants: {
14
+ orientation: {
15
+ horizontal: 'h-px w-full bg-border',
16
+ vertical: 'w-px h-full bg-border',
17
+ },
18
+ labeled: {
19
+ true: 'flex items-center',
20
+ false: '',
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ orientation: 'horizontal',
25
+ labeled: 'false',
26
+ },
27
+ })
28
+
29
+ /** Class for each flanking line in the labeled-divider variant. */
30
+ export const separatorLineClass = 'h-px flex-1 bg-border'
31
+
32
+ /** Class for the centered label in the labeled-divider variant. */
33
+ export const separatorLabelClass =
34
+ 'px-3 text-xs uppercase tracking-wide text-muted-foreground'
@@ -0,0 +1,53 @@
1
+ import type { AccessibilityProps } from '../shared/index.ts'
2
+
3
+ export type SeparatorOrientation = 'horizontal' | 'vertical'
4
+
5
+ export interface SeparatorProps {
6
+ /** Orientation of the separator. Defaults to `'horizontal'`. */
7
+ orientation?: SeparatorOrientation
8
+ /**
9
+ * When `true` (the default) the separator is purely visual and is hidden
10
+ * from assistive tech (`role="none"`). When `false` it is exposed as a
11
+ * semantic `role="separator"` with the appropriate `aria-orientation`.
12
+ */
13
+ decorative?: boolean
14
+ }
15
+
16
+ /**
17
+ * ARIA attributes for a separator. Extends the shared base with
18
+ * `aria-orientation`, which is specific to the `separator` role.
19
+ */
20
+ export interface SeparatorAriaProps extends Partial<AccessibilityProps> {
21
+ 'aria-orientation'?: SeparatorOrientation
22
+ }
23
+
24
+ export interface SeparatorAPI {
25
+ /** ARIA attributes to spread on the element. */
26
+ ariaProps: SeparatorAriaProps
27
+ /** Data attributes for CSS styling hooks. */
28
+ dataAttributes: Record<string, string>
29
+ }
30
+
31
+ /**
32
+ * Headless separator core — computes ARIA semantics and data attributes for
33
+ * a separator rule. JSX-free so it can be shared across framework adapters.
34
+ *
35
+ * Per ARIA, a decorative separator must not be announced (`role="none"`),
36
+ * while a semantic one exposes `role="separator"` and `aria-orientation`.
37
+ */
38
+ export function createSeparator(props: SeparatorProps = {}): SeparatorAPI {
39
+ const { orientation = 'horizontal', decorative = true } = props
40
+
41
+ const ariaProps: SeparatorAriaProps = decorative
42
+ ? { role: 'none' }
43
+ : { role: 'separator', 'aria-orientation': orientation }
44
+
45
+ const dataAttributes: Record<string, string> = {
46
+ 'data-orientation': orientation,
47
+ }
48
+
49
+ return {
50
+ ariaProps,
51
+ dataAttributes,
52
+ }
53
+ }
@@ -0,0 +1,14 @@
1
+ export {
2
+ createSocialAuthButton,
3
+ PROVIDER_LABELS,
4
+ PROVIDER_MONO,
5
+ type SocialProvider,
6
+ type SocialAuthButtonProps,
7
+ type SocialAuthButtonAPI,
8
+ } from './social-auth-button.js'
9
+
10
+ export {
11
+ socialAuthButtonVariants,
12
+ socialAuthRowVariants,
13
+ lastUsedBadgeVariants,
14
+ } from './social-auth-button.styles.js'
@@ -0,0 +1,16 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /** Wrapper around an individual social auth button (positions the lastUsed badge). */
4
+ export const socialAuthButtonVariants = cva({
5
+ base: 'relative',
6
+ })
7
+
8
+ /** Responsive grid wrapper: one column on mobile, two columns from `sm` up. */
9
+ export const socialAuthRowVariants = cva({
10
+ base: 'grid grid-cols-1 sm:grid-cols-2 gap-3',
11
+ })
12
+
13
+ /** Floating "Last used" badge anchored to the top-right of a button. */
14
+ export const lastUsedBadgeVariants = cva({
15
+ base: 'absolute -top-2 -right-2 rounded-full bg-primary text-primary-foreground text-[10px] px-1.5',
16
+ })
@@ -0,0 +1,51 @@
1
+ export type SocialProvider = 'google' | 'github' | 'microsoft' | 'apple'
2
+
3
+ /** "Continue with …" label shown on each provider's button. */
4
+ export const PROVIDER_LABELS: Record<SocialProvider, string> = {
5
+ google: 'Continue with Google',
6
+ github: 'Continue with GitHub',
7
+ microsoft: 'Continue with Microsoft',
8
+ apple: 'Continue with Apple',
9
+ }
10
+
11
+ /**
12
+ * Which providers render their brand mark monochrome (currentColor). GitHub and
13
+ * Apple are monochrome by design so they read correctly on dark/outline
14
+ * surfaces; Google and Microsoft keep their full brand color.
15
+ */
16
+ export const PROVIDER_MONO: Record<SocialProvider, boolean> = {
17
+ google: false,
18
+ github: true,
19
+ microsoft: false,
20
+ apple: true,
21
+ }
22
+
23
+ export interface SocialAuthButtonProps {
24
+ /** Identity provider this button authenticates against. */
25
+ provider: SocialProvider
26
+ }
27
+
28
+ export interface SocialAuthButtonAPI {
29
+ /** Data attributes for CSS styling hooks. */
30
+ dataAttributes: Record<string, string>
31
+ /** "Continue with …" label for the provider. */
32
+ label: string
33
+ /** Whether the brand mark should render monochrome. */
34
+ mono: boolean
35
+ }
36
+
37
+ /**
38
+ * Framework-agnostic core for a social sign-in button. Returns the provider
39
+ * label, monochrome hint, and the `data-provider` styling hook. No React/JSX.
40
+ */
41
+ export function createSocialAuthButton(
42
+ props: SocialAuthButtonProps,
43
+ ): SocialAuthButtonAPI {
44
+ const { provider } = props
45
+
46
+ return {
47
+ dataAttributes: { 'data-provider': provider },
48
+ label: PROVIDER_LABELS[provider],
49
+ mono: PROVIDER_MONO[provider],
50
+ }
51
+ }
@@ -0,0 +1,14 @@
1
+ export {
2
+ STICKY_NOTE_COLORS,
3
+ nextStickyColor,
4
+ createStickyNote,
5
+ type StickyNoteColor,
6
+ type CreateStickyNoteOptions,
7
+ type StickyNoteAPI,
8
+ } from './sticky-note.js'
9
+
10
+ export {
11
+ stickyNoteVariants,
12
+ stickyNoteTextClass,
13
+ stickyNoteAuthorClass,
14
+ } from './sticky-note.styles.js'
@@ -0,0 +1,37 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * Note container with color variant.
5
+ *
6
+ * Sticky notes are intrinsically colorful — an approved exception to the
7
+ * semantic-token-only rule. Each color maps to a soft tint background with a
8
+ * readable dark text.
9
+ */
10
+ export const stickyNoteVariants = cva({
11
+ base: [
12
+ 'relative inline-flex flex-col gap-2 rounded-md p-4',
13
+ 'shadow-md',
14
+ 'min-w-[160px] min-h-[120px]',
15
+ ].join(' '),
16
+ variants: {
17
+ color: {
18
+ yellow: 'bg-yellow-100 text-yellow-900',
19
+ pink: 'bg-pink-100 text-pink-900',
20
+ blue: 'bg-blue-100 text-blue-900',
21
+ green: 'bg-green-100 text-green-900',
22
+ purple: 'bg-purple-100 text-purple-900',
23
+ orange: 'bg-orange-100 text-orange-900',
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ color: 'yellow',
28
+ },
29
+ })
30
+
31
+ /** Class for the editable/static text area inside the note. */
32
+ export const stickyNoteTextClass =
33
+ 'flex-1 w-full resize-none bg-transparent text-sm leading-relaxed placeholder:opacity-60 outline-none'
34
+
35
+ /** Class for the author chip rendered in the note footer. */
36
+ export const stickyNoteAuthorClass =
37
+ 'mt-auto text-xs font-medium opacity-70 truncate'
@@ -0,0 +1,47 @@
1
+ /** The available color variants for a sticky note. */
2
+ export type StickyNoteColor = 'yellow' | 'pink' | 'blue' | 'green' | 'purple' | 'orange'
3
+
4
+ /** Ordered list of all sticky note colors — used for cycling and iteration. */
5
+ export const STICKY_NOTE_COLORS: StickyNoteColor[] = [
6
+ 'yellow',
7
+ 'pink',
8
+ 'blue',
9
+ 'green',
10
+ 'purple',
11
+ 'orange',
12
+ ]
13
+
14
+ /**
15
+ * Returns the next color in the cycle. Wraps from the last color back to the
16
+ * first. Useful for "add note" flows that auto-assign colors in sequence.
17
+ */
18
+ export function nextStickyColor(color: StickyNoteColor): StickyNoteColor {
19
+ const index = STICKY_NOTE_COLORS.indexOf(color)
20
+ const next = (index + 1) % STICKY_NOTE_COLORS.length
21
+ return STICKY_NOTE_COLORS[next]
22
+ }
23
+
24
+ export interface CreateStickyNoteOptions {
25
+ /** Color of the note. */
26
+ color: StickyNoteColor
27
+ }
28
+
29
+ export interface StickyNoteAPI {
30
+ /** Data attributes for styling hooks; spread onto the note container. */
31
+ dataAttributes: { 'data-color': StickyNoteColor }
32
+ /** ARIA props for the note container. */
33
+ ariaProps: { role: 'group' }
34
+ }
35
+
36
+ /**
37
+ * Build the framework-agnostic props for a sticky note container.
38
+ *
39
+ * Returns `role="group"` plus the `data-color` attribute that adapters use
40
+ * as a styling hook. Logic is pure and SSR-safe — no Math.random, no Date.
41
+ */
42
+ export function createStickyNote(options: CreateStickyNoteOptions): StickyNoteAPI {
43
+ return {
44
+ dataAttributes: { 'data-color': options.color },
45
+ ariaProps: { role: 'group' },
46
+ }
47
+ }
@@ -0,0 +1,13 @@
1
+ export {
2
+ formatPrompt,
3
+ createTerminal,
4
+ type TerminalLineKind,
5
+ type TerminalLine,
6
+ type TerminalAPI,
7
+ } from './terminal.js'
8
+
9
+ export {
10
+ terminalVariants,
11
+ terminalLineVariants,
12
+ terminalPromptClass,
13
+ } from './terminal.styles.js'
@@ -0,0 +1,56 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * The outer scroll container — a dark monospaced surface that houses all
5
+ * terminal lines. Uses semantic card tokens so it inherits the active theme.
6
+ */
7
+ export const terminalVariants = cva({
8
+ base: [
9
+ 'relative rounded-lg border border-border bg-card font-mono text-sm',
10
+ 'overflow-y-auto overflow-x-auto',
11
+ 'p-4 space-y-0.5',
12
+ ].join(' '),
13
+ variants: {
14
+ maxHeight: {
15
+ sm: 'max-h-48',
16
+ md: 'max-h-80',
17
+ lg: 'max-h-[32rem]',
18
+ none: '',
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ maxHeight: 'md',
23
+ },
24
+ })
25
+
26
+ /**
27
+ * A single terminal line. The `kind` variant maps each semantic category to
28
+ * the appropriate semantic token class — no raw color values inline.
29
+ */
30
+ export const terminalLineVariants = cva({
31
+ base: 'block whitespace-pre-wrap break-all leading-relaxed',
32
+ variants: {
33
+ kind: {
34
+ /** The user-typed command: full foreground, bold weight. */
35
+ command: 'text-foreground font-semibold',
36
+ /** Normal program output. */
37
+ stdout: 'text-foreground',
38
+ /** Error / stderr output — uses the destructive semantic token. */
39
+ stderr: 'text-destructive',
40
+ /** Informational summary lines — muted, visually de-emphasised. */
41
+ info: 'text-muted-foreground',
42
+ /**
43
+ * Success / passing lines — green-family.
44
+ * Maps to `text-emerald-500` via the Tailwind semantic alias used
45
+ * across the design system; downstream themes can override this token.
46
+ */
47
+ success: 'text-emerald-500 dark:text-emerald-400',
48
+ },
49
+ },
50
+ defaultVariants: {
51
+ kind: 'stdout',
52
+ },
53
+ })
54
+
55
+ /** The prompt symbol (`$`, `❯`, …) preceding a command line. */
56
+ export const terminalPromptClass = 'select-none mr-2 text-muted-foreground'
@@ -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'