@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,137 @@
1
+ ---
2
+ import {
3
+ createWizard,
4
+ getStepState,
5
+ wizardVariants,
6
+ wizardRailVariants,
7
+ wizardStepItemVariants,
8
+ wizardStepIndicatorVariants,
9
+ wizardStepLabelVariants,
10
+ wizardStepOptionalClass,
11
+ wizardConnectorVariants,
12
+ wizardContentClass,
13
+ wizardFooterClass,
14
+ wizardPrimaryButtonVariants,
15
+ wizardSecondaryButtonVariants,
16
+ type WizardStep,
17
+ type WizardOrientation,
18
+ } from '../wizard/index.ts'
19
+ import { cn } from '../shared/index.ts'
20
+
21
+ interface Props extends astroHTML.JSX.HTMLAttributes {
22
+ /** Step definitions for the rail. */
23
+ steps: WizardStep[]
24
+ /** Current step index (0-based). */
25
+ currentStep?: number
26
+ /** Orientation of the step rail. */
27
+ orientation?: WizardOrientation
28
+ /** Label for the Next button. */
29
+ nextLabel?: string
30
+ /** Label for the Back button. */
31
+ backLabel?: string
32
+ /** Label for the button on the final step. */
33
+ completeLabel?: string
34
+ /** Label for the Skip button (shown on optional steps only). */
35
+ skipLabel?: string
36
+ }
37
+
38
+ const {
39
+ steps,
40
+ currentStep = 0,
41
+ orientation = 'vertical',
42
+ nextLabel = 'Next',
43
+ backLabel = 'Back',
44
+ completeLabel = 'Complete',
45
+ skipLabel = 'Skip',
46
+ class: className,
47
+ ...rest
48
+ } = Astro.props
49
+
50
+ const stepStates = getStepState(steps, currentStep)
51
+ const isLastStep = currentStep === steps.length - 1
52
+ const isOptional = steps[currentStep]?.optional ?? false
53
+ const { ariaProps, dataAttributes } = createWizard({
54
+ currentIndex: currentStep,
55
+ count: steps.length,
56
+ orientation,
57
+ })
58
+ ---
59
+
60
+ <div class={cn(wizardVariants({ orientation }), className)} {...ariaProps} {...dataAttributes} {...rest}>
61
+ <!-- Step Rail -->
62
+ <nav aria-label="Progress" class={wizardRailVariants({ orientation })}>
63
+ {
64
+ stepStates.map((state, i) => {
65
+ const isLast = i === stepStates.length - 1
66
+ return (
67
+ <>
68
+ <div class={wizardStepItemVariants({ orientation, status: state.status })}>
69
+ <span
70
+ aria-current={state.status === 'current' ? 'step' : undefined}
71
+ class={wizardStepIndicatorVariants({ status: state.status })}
72
+ >
73
+ {state.status === 'complete' ? (
74
+ <svg
75
+ aria-hidden="true"
76
+ xmlns="http://www.w3.org/2000/svg"
77
+ viewBox="0 0 24 24"
78
+ fill="none"
79
+ stroke="currentColor"
80
+ stroke-width="2.5"
81
+ stroke-linecap="round"
82
+ stroke-linejoin="round"
83
+ class="size-4"
84
+ >
85
+ <polyline points="20 6 9 17 4 12" />
86
+ </svg>
87
+ ) : (
88
+ <span>{state.index + 1}</span>
89
+ )}
90
+ </span>
91
+ <div class="flex flex-col">
92
+ <span class={wizardStepLabelVariants({ status: state.status })}>
93
+ {state.step.label}
94
+ </span>
95
+ {state.step.optional && (
96
+ <span class={wizardStepOptionalClass}>(Optional)</span>
97
+ )}
98
+ </div>
99
+ </div>
100
+ {!isLast && (
101
+ <div
102
+ aria-hidden="true"
103
+ class={wizardConnectorVariants({
104
+ orientation,
105
+ complete: state.status === 'complete' ? 'true' : 'false',
106
+ })}
107
+ />
108
+ )}
109
+ </>
110
+ )
111
+ })
112
+ }
113
+ </nav>
114
+
115
+ <!-- Content + Footer -->
116
+ <div class={wizardContentClass}>
117
+ <div class="flex-1">
118
+ <slot />
119
+ </div>
120
+
121
+ <div class={wizardFooterClass}>
122
+ <button type="button" disabled={currentStep === 0} class={wizardSecondaryButtonVariants()}>
123
+ {backLabel}
124
+ </button>
125
+
126
+ {isOptional && (
127
+ <button type="button" class={wizardSecondaryButtonVariants()}>
128
+ {skipLabel}
129
+ </button>
130
+ )}
131
+
132
+ <button type="button" class={wizardPrimaryButtonVariants()}>
133
+ {isLastStep ? completeLabel : nextLabel}
134
+ </button>
135
+ </div>
136
+ </div>
137
+ </div>
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,88 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * Grid container that lays out the speaking orbs.
5
+ *
6
+ * Column count and gap are applied via inline `gridTemplateColumns` in
7
+ * adapters (dynamic count); this cva handles only the non-dynamic layout
8
+ * concerns — display, alignment, and padding.
9
+ */
10
+ export const audioRoomVariants = cva({
11
+ base: 'grid place-items-center gap-6 p-6 w-full',
12
+ variants: {},
13
+ defaultVariants: {},
14
+ })
15
+
16
+ /**
17
+ * Wrapper cell for a single orb + name label combination.
18
+ * Keeps the avatar and label centred as a column.
19
+ */
20
+ export const audioRoomOrbCellVariants = cva({
21
+ base: 'flex flex-col items-center gap-2',
22
+ variants: {},
23
+ defaultVariants: {},
24
+ })
25
+
26
+ /**
27
+ * The circular avatar / speaking indicator.
28
+ *
29
+ * - `speaking` adds an animated ring using the primary colour.
30
+ * - `muted` dims the orb slightly to signal a silenced microphone.
31
+ */
32
+ export const speakingOrbVariants = cva({
33
+ base: [
34
+ 'relative inline-flex items-center justify-center',
35
+ 'size-20 rounded-full',
36
+ 'bg-muted text-muted-foreground font-semibold text-xl select-none',
37
+ 'overflow-hidden',
38
+ 'transition-shadow duration-300',
39
+ ].join(' '),
40
+ variants: {
41
+ speaking: {
42
+ true: [
43
+ 'ring-4 ring-primary ring-offset-2 ring-offset-background',
44
+ 'shadow-[0_0_0_4px_hsl(var(--primary)/0.25),0_0_16px_4px_hsl(var(--primary)/0.35)]',
45
+ ].join(' '),
46
+ false: 'ring-2 ring-border',
47
+ },
48
+ muted: {
49
+ true: 'opacity-60',
50
+ false: '',
51
+ },
52
+ },
53
+ defaultVariants: {
54
+ speaking: 'false',
55
+ muted: 'false',
56
+ },
57
+ })
58
+
59
+ /** Name label rendered below each orb. */
60
+ export const audioRoomNameLabelClass =
61
+ 'text-sm font-medium text-foreground text-center max-w-[6rem] truncate'
62
+
63
+ /**
64
+ * Muted badge — a small indicator overlaid at the bottom-right of the orb
65
+ * to show the microphone is off.
66
+ */
67
+ export const audioRoomMutedBadgeClass = [
68
+ 'absolute bottom-0 right-0',
69
+ 'inline-flex items-center justify-center',
70
+ 'size-6 rounded-full',
71
+ 'bg-destructive text-destructive-foreground',
72
+ 'text-[10px] leading-none',
73
+ 'border-2 border-background',
74
+ 'translate-x-1 translate-y-1',
75
+ ].join(' ')
76
+
77
+ /**
78
+ * Hand-raise badge — shown at the top-right of the orb.
79
+ */
80
+ export const audioRoomHandBadgeClass = [
81
+ 'absolute top-0 right-0',
82
+ 'inline-flex items-center justify-center',
83
+ 'size-6 rounded-full',
84
+ 'bg-warning text-warning-foreground',
85
+ 'text-[12px] leading-none',
86
+ 'border-2 border-background',
87
+ 'translate-x-1 -translate-y-1',
88
+ ].join(' ')
@@ -0,0 +1,70 @@
1
+ /** A participant in an audio-only room. */
2
+ export interface AudioParticipant {
3
+ /** Unique participant identifier. */
4
+ id: string
5
+ /** Display name shown below the orb. */
6
+ name: string
7
+ /** Optional avatar image URL. Falls back to initials when absent. */
8
+ avatarUrl?: string
9
+ /** Whether this participant is currently speaking. */
10
+ speaking?: boolean
11
+ /** Whether this participant's microphone is muted. */
12
+ muted?: boolean
13
+ /** Whether this participant has raised their hand. */
14
+ handRaised?: boolean
15
+ }
16
+
17
+ export interface AudioRoomAPI {
18
+ /** ARIA attributes to spread on the room container element. */
19
+ ariaProps: { role: 'group' }
20
+ /** Data attributes for styling hooks. */
21
+ dataAttributes: Record<string, string>
22
+ }
23
+
24
+ /**
25
+ * Derive up to two uppercase initials from a display name.
26
+ *
27
+ * Examples: "Alice" → "A", "Bob Smith" → "BS", "Dr. Jane Doe" → "JD"
28
+ * (only word-initial alphabetic characters are used).
29
+ */
30
+ export function getInitials(name: string): string {
31
+ const words = name
32
+ .trim()
33
+ .split(/\s+/)
34
+ .filter((w) => /[A-Za-z]/.test(w))
35
+ if (words.length === 0) return ''
36
+ if (words.length === 1) return words[0][0].toUpperCase()
37
+ return (words[0][0] + words[words.length - 1][0]).toUpperCase()
38
+ }
39
+
40
+ /**
41
+ * Determine the number of grid columns for the orb grid.
42
+ *
43
+ * | Count | Columns |
44
+ * |--------|---------|
45
+ * | 1 | 1 |
46
+ * | 2–4 | 2 |
47
+ * | 5–9 | 3 |
48
+ * | 10+ | 4 |
49
+ */
50
+ export function orbColumns(count: number): number {
51
+ if (count <= 1) return 1
52
+ if (count <= 4) return 2
53
+ if (count <= 9) return 3
54
+ return 4
55
+ }
56
+
57
+ /**
58
+ * Build the framework-agnostic container props for an audio room.
59
+ *
60
+ * Returns `role="group"` plus data attributes that adapters spread onto
61
+ * their outermost container element.
62
+ */
63
+ export function createAudioRoom(): AudioRoomAPI {
64
+ return {
65
+ ariaProps: { role: 'group' },
66
+ dataAttributes: {
67
+ 'data-component': 'audio-room',
68
+ },
69
+ }
70
+ }
@@ -0,0 +1,16 @@
1
+ export {
2
+ createAudioRoom,
3
+ getInitials,
4
+ orbColumns,
5
+ type AudioParticipant,
6
+ type AudioRoomAPI,
7
+ } from './audio-room.js'
8
+
9
+ export {
10
+ audioRoomVariants,
11
+ audioRoomOrbCellVariants,
12
+ speakingOrbVariants,
13
+ audioRoomNameLabelClass,
14
+ audioRoomMutedBadgeClass,
15
+ audioRoomHandBadgeClass,
16
+ } from './audio-room.styles.js'
@@ -0,0 +1,63 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * Toolbar container — flex row, centred, with a card background and gap
5
+ * between the control buttons.
6
+ */
7
+ export const callControlsVariants = cva({
8
+ base: [
9
+ 'inline-flex items-center justify-center gap-2',
10
+ 'rounded-xl bg-card px-4 py-3',
11
+ 'shadow-sm border border-border',
12
+ ].join(' '),
13
+ variants: {
14
+ size: {
15
+ sm: 'gap-1.5 px-3 py-2',
16
+ md: 'gap-2 px-4 py-3',
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ size: 'md',
21
+ },
22
+ })
23
+
24
+ /**
25
+ * Individual call-control button.
26
+ *
27
+ * `tone` variants:
28
+ * - `default` → neutral muted appearance (standard inactive controls)
29
+ * - `active` → primary/highlighted (e.g. mic on, screen-share running)
30
+ * - `destructive` → red/danger (leave button or a muted mic indicator)
31
+ *
32
+ * `size` variants match the toolbar container.
33
+ */
34
+ export const callControlButtonVariants = cva({
35
+ base: [
36
+ 'inline-flex flex-col items-center justify-center rounded-full',
37
+ 'font-medium transition-colors',
38
+ 'focus-visible:outline-none focus-visible:ring-2',
39
+ 'focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
40
+ 'disabled:pointer-events-none disabled:opacity-50',
41
+ 'cursor-pointer',
42
+ ].join(' '),
43
+ variants: {
44
+ size: {
45
+ sm: 'size-9 text-xs',
46
+ md: 'size-11 text-xs',
47
+ },
48
+ tone: {
49
+ default: 'bg-muted text-foreground hover:bg-muted/80',
50
+ active: 'bg-primary text-primary-foreground hover:bg-primary/90',
51
+ destructive:
52
+ 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
53
+ },
54
+ },
55
+ defaultVariants: {
56
+ size: 'md',
57
+ tone: 'default',
58
+ },
59
+ })
60
+
61
+ /** Label beneath the icon inside a call-control button. */
62
+ export const callControlLabelClass =
63
+ 'sr-only text-[10px] leading-none mt-0.5'
@@ -0,0 +1,79 @@
1
+ import type { AccessibilityProps } from '../shared/index.ts'
2
+
3
+ /** Visual/semantic tone of a call control button. */
4
+ export type CallControlTone = 'default' | 'active' | 'destructive'
5
+
6
+ /**
7
+ * Toggleable state for controls like mic or camera.
8
+ * `'on'` = currently active (mic/camera live).
9
+ * `'off'` = currently muted/disabled.
10
+ */
11
+ export type CallControlState = 'on' | 'off'
12
+
13
+ /**
14
+ * A single control descriptor used by adapters to drive button rendering.
15
+ */
16
+ export interface CallControlItem {
17
+ /** Unique identifier for this control. */
18
+ id: string
19
+ /** Visible label (also used as aria-label). */
20
+ label: string
21
+ /** Optional icon to render inside the button. */
22
+ icon?: string
23
+ /** Explicit tone override; when omitted `controlTone` is used with `state`. */
24
+ tone?: CallControlTone
25
+ /** Toggleable state (present only for toggleable controls). */
26
+ pressed?: boolean
27
+ }
28
+
29
+ export interface CallControlsProps {
30
+ /** Controls to render. */
31
+ controls?: CallControlItem[]
32
+ }
33
+
34
+ export interface CallControlsAPI {
35
+ /** ARIA attributes to spread on the toolbar element (`role="toolbar"`). */
36
+ ariaProps: Partial<AccessibilityProps>
37
+ /** Data attributes for styling hooks. */
38
+ dataAttributes: Record<string, string>
39
+ }
40
+
41
+ /**
42
+ * Derive the visual tone for a control given its kind and current toggle state.
43
+ *
44
+ * Convention used by easyloops meeting toolbar:
45
+ * - `leave` / `end` → always `'destructive'`
46
+ * - any control that is `'off'` (muted/stopped) → `'destructive'` to signal
47
+ * the user that something is disabled (e.g. mic muted = red indicator).
48
+ * - a control that is `'on'` → `'active'` (lit up / highlighted).
49
+ * - otherwise → `'default'` (neutral).
50
+ */
51
+ export function controlTone(
52
+ kind: string,
53
+ state: CallControlState | undefined,
54
+ ): CallControlTone {
55
+ const normalised = kind.toLowerCase()
56
+ if (normalised === 'leave' || normalised === 'end') return 'destructive'
57
+ if (state === 'off') return 'destructive'
58
+ if (state === 'on') return 'active'
59
+ return 'default'
60
+ }
61
+
62
+ /**
63
+ * Build the framework-agnostic toolbar props for a call-controls bar.
64
+ *
65
+ * Returns `role="toolbar"` plus `aria-label="Call controls"` and data
66
+ * attributes; adapters spread these onto their container element.
67
+ */
68
+ export function createCallControls(_props: CallControlsProps = {}): CallControlsAPI {
69
+ const ariaProps: Partial<AccessibilityProps> = {
70
+ role: 'toolbar',
71
+ 'aria-label': 'Call controls',
72
+ }
73
+
74
+ const dataAttributes: Record<string, string> = {
75
+ 'data-component': 'call-controls',
76
+ }
77
+
78
+ return { ariaProps, dataAttributes }
79
+ }
@@ -0,0 +1,15 @@
1
+ export {
2
+ controlTone,
3
+ createCallControls,
4
+ type CallControlTone,
5
+ type CallControlState,
6
+ type CallControlItem,
7
+ type CallControlsProps,
8
+ type CallControlsAPI,
9
+ } from './call-controls.js'
10
+
11
+ export {
12
+ callControlsVariants,
13
+ callControlButtonVariants,
14
+ callControlLabelClass,
15
+ } from './call-controls.styles.js'
@@ -0,0 +1,76 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /** Outer container that wraps the list and optional progress summary. */
4
+ export const checklistContainerClass = 'flex flex-col gap-2'
5
+
6
+ /** The `role="list"` element holding all item rows. */
7
+ export const checklistListClass = 'flex flex-col gap-1'
8
+
9
+ /**
10
+ * A single item row — a button with checkbox semantics.
11
+ *
12
+ * The `checked` variant applies muted text and a line-through on the label
13
+ * to communicate completion without relying solely on the checkbox visual.
14
+ */
15
+ export const checklistItemVariants = cva({
16
+ base: [
17
+ 'flex items-start gap-3 rounded-md px-2 py-2 text-left',
18
+ 'transition-colors hover:bg-accent',
19
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
20
+ 'disabled:pointer-events-none disabled:opacity-50',
21
+ ].join(' '),
22
+ variants: {
23
+ checked: {
24
+ true: 'text-muted-foreground',
25
+ false: 'text-foreground',
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ checked: 'false',
30
+ },
31
+ })
32
+
33
+ /**
34
+ * The visual checkbox box (the square indicator beside the label).
35
+ *
36
+ * The `checked` variant fills the box with the primary colour and shows a
37
+ * checkmark via a border trick; the unchecked state shows an empty border.
38
+ */
39
+ export const checklistBoxVariants = cva({
40
+ base: [
41
+ 'mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded',
42
+ 'border transition-colors',
43
+ ].join(' '),
44
+ variants: {
45
+ checked: {
46
+ true: 'border-primary bg-primary text-primary-foreground',
47
+ false: 'border-input bg-background',
48
+ },
49
+ },
50
+ defaultVariants: {
51
+ checked: 'false',
52
+ },
53
+ })
54
+
55
+ /** Wrapper for label + optional description text. */
56
+ export const checklistLabelWrapClass = 'flex flex-col gap-0.5 min-w-0'
57
+
58
+ /** The item's primary label. Strikethrough is applied when the item is checked. */
59
+ export const checklistLabelVariants = cva({
60
+ base: 'text-sm font-medium leading-none transition-colors',
61
+ variants: {
62
+ checked: {
63
+ true: 'line-through text-muted-foreground',
64
+ false: 'text-foreground',
65
+ },
66
+ },
67
+ defaultVariants: {
68
+ checked: 'false',
69
+ },
70
+ })
71
+
72
+ /** Optional description — always in muted text, smaller than the label. */
73
+ export const checklistDescriptionClass = 'text-xs text-muted-foreground'
74
+
75
+ /** Progress summary line shown below the list when `showProgress` is true. */
76
+ export const checklistProgressClass = 'text-xs text-muted-foreground tabular-nums'
@@ -0,0 +1,62 @@
1
+ /** A single item in a checklist. */
2
+ export interface ChecklistItemData {
3
+ /** Unique identifier for this item. */
4
+ id: string
5
+ /** Display label for the item. */
6
+ label: string
7
+ /** Whether the item is completed. */
8
+ checked?: boolean
9
+ /** Optional additional description text. */
10
+ description?: string
11
+ }
12
+
13
+ export interface ChecklistAPI {
14
+ /** ARIA attributes to spread on the list container (`role="list"`). */
15
+ ariaProps: { role: 'list' }
16
+ /** Data attributes for styling hooks. */
17
+ dataAttributes: Record<string, string>
18
+ }
19
+
20
+ /**
21
+ * Build the framework-agnostic container props for a checklist.
22
+ *
23
+ * Returns `role="list"` plus a `data-component` attribute. Adapters spread
24
+ * these onto their container element.
25
+ */
26
+ export function createChecklist(): ChecklistAPI {
27
+ return {
28
+ ariaProps: { role: 'list' },
29
+ dataAttributes: { 'data-component': 'checklist' },
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Compute progress statistics for a checklist.
35
+ *
36
+ * Pure function — does not mutate the array.
37
+ */
38
+ export function checklistProgress(items: ChecklistItemData[]): {
39
+ completed: number
40
+ total: number
41
+ fraction: number
42
+ } {
43
+ const total = items.length
44
+ const completed = items.filter((item) => item.checked).length
45
+ const fraction = total === 0 ? 0 : completed / total
46
+ return { completed, total, fraction }
47
+ }
48
+
49
+ /**
50
+ * Toggle a single item's `checked` state by id.
51
+ *
52
+ * Pure — returns a new array; the original is not mutated.
53
+ * Items whose id does not match are returned unchanged (same reference).
54
+ */
55
+ export function toggleChecklistItem(
56
+ items: ChecklistItemData[],
57
+ id: string,
58
+ ): ChecklistItemData[] {
59
+ return items.map((item) =>
60
+ item.id === id ? { ...item, checked: !item.checked } : item,
61
+ )
62
+ }
@@ -0,0 +1,18 @@
1
+ export {
2
+ createChecklist,
3
+ checklistProgress,
4
+ toggleChecklistItem,
5
+ type ChecklistItemData,
6
+ type ChecklistAPI,
7
+ } from './checklist.js'
8
+
9
+ export {
10
+ checklistContainerClass,
11
+ checklistListClass,
12
+ checklistItemVariants,
13
+ checklistBoxVariants,
14
+ checklistLabelWrapClass,
15
+ checklistLabelVariants,
16
+ checklistDescriptionClass,
17
+ checklistProgressClass,
18
+ } from './checklist.styles.js'
@@ -0,0 +1,40 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * The outer bar — a thin, full-width strip that sits at the bottom of an IDE
5
+ * pane. Uses a monospace font stack so numeric values (line, col) don't shift
6
+ * width as they update.
7
+ */
8
+ export const editorStatusBarVariants = cva({
9
+ base: [
10
+ 'flex items-center justify-between',
11
+ 'w-full h-6 px-2 gap-4',
12
+ 'text-xs font-mono',
13
+ 'bg-muted text-muted-foreground',
14
+ 'border-t border-border',
15
+ 'select-none',
16
+ ].join(' '),
17
+ variants: {},
18
+ defaultVariants: {},
19
+ })
20
+
21
+ /** The left or right group that holds a cluster of segments. */
22
+ export const statusBarGroupClass = 'flex items-center gap-3 overflow-hidden'
23
+
24
+ /**
25
+ * A single segment pill. The `tone` variant maps to semantic token classes so
26
+ * adapters never inline colour values.
27
+ */
28
+ export const statusBarSegmentVariants = cva({
29
+ base: 'inline-flex items-center whitespace-nowrap truncate leading-none',
30
+ variants: {
31
+ tone: {
32
+ default: 'text-muted-foreground',
33
+ muted: 'text-muted-foreground/60',
34
+ accent: 'text-primary',
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ tone: 'default',
39
+ },
40
+ })