@refraction-ui/astro 0.11.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/astro-audio-room/AudioRoom.astro +81 -0
- package/dist/astro-audio-room/index.ts +1 -0
- package/dist/astro-call-controls/CallControls.astro +57 -0
- package/dist/astro-call-controls/index.ts +1 -0
- package/dist/astro-checklist/Checklist.astro +76 -0
- package/dist/astro-checklist/index.ts +1 -0
- package/dist/astro-editor-status-bar/EditorStatusBar.astro +92 -0
- package/dist/astro-editor-status-bar/index.ts +1 -0
- package/dist/astro-editor-tabs/EditorTabs.astro +74 -0
- package/dist/astro-editor-tabs/index.ts +1 -0
- package/dist/astro-empty-state/EmptyState.astro +57 -0
- package/dist/astro-empty-state/index.ts +1 -0
- package/dist/astro-floating-reactions/FloatingReactions.astro +56 -0
- package/dist/astro-floating-reactions/index.ts +1 -0
- package/dist/astro-flow-editor/FlowEditor.astro +99 -0
- package/dist/astro-flow-editor/index.ts +3 -0
- package/dist/astro-graph-view/GraphView.astro +109 -0
- package/dist/astro-graph-view/index.ts +1 -0
- package/dist/astro-infinite-canvas/InfiniteCanvas.astro +43 -0
- package/dist/astro-infinite-canvas/index.ts +1 -0
- package/dist/astro-input/Input.astro +5 -1
- package/dist/astro-live-captions/LiveCaptions.astro +54 -0
- package/dist/astro-live-captions/index.ts +1 -0
- package/dist/astro-live-cursors/LiveCursors.astro +55 -0
- package/dist/astro-live-cursors/index.ts +1 -0
- package/dist/astro-live-transcript/LiveTranscript.astro +55 -0
- package/dist/astro-live-transcript/index.ts +1 -0
- package/dist/astro-mini-map/MiniMap.astro +74 -0
- package/dist/astro-mini-map/index.ts +1 -0
- package/dist/astro-password-input/PasswordInput.astro +102 -0
- package/dist/astro-password-input/index.ts +1 -0
- package/dist/astro-pre-call-lobby/PreCallLobby.astro +202 -0
- package/dist/astro-pre-call-lobby/index.ts +1 -0
- package/dist/astro-radial-gauge/RadialGauge.astro +144 -0
- package/dist/astro-radial-gauge/index.ts +1 -0
- package/dist/astro-rating-scale/RatingScale.astro +73 -0
- package/dist/astro-rating-scale/index.ts +1 -0
- package/dist/astro-segmented-control/SegmentedControl.astro +106 -0
- package/dist/astro-segmented-control/SegmentedControlItem.astro +32 -0
- package/dist/astro-segmented-control/index.ts +1 -0
- package/dist/astro-separator/Separator.astro +49 -0
- package/dist/astro-separator/index.ts +1 -0
- package/dist/astro-social-auth-button/SocialAuthButton.astro +110 -0
- package/dist/astro-social-auth-button/SocialAuthRow.astro +13 -0
- package/dist/astro-social-auth-button/index.ts +1 -0
- package/dist/astro-sticky-note/StickyNote.astro +55 -0
- package/dist/astro-sticky-note/index.ts +1 -0
- package/dist/astro-terminal/Terminal.astro +67 -0
- package/dist/astro-terminal/index.ts +1 -0
- package/dist/astro-test-results/TestResults.astro +76 -0
- package/dist/astro-test-results/index.ts +1 -0
- package/dist/astro-timeline/Timeline.astro +59 -0
- package/dist/astro-timeline/index.ts +1 -0
- package/dist/astro-video-grid/VideoGrid.astro +182 -0
- package/dist/astro-video-grid/index.ts +1 -0
- package/dist/astro-video-tile/VideoTile.astro +104 -0
- package/dist/astro-video-tile/index.ts +1 -0
- package/dist/astro-wizard/Wizard.astro +137 -0
- package/dist/astro-wizard/index.ts +1 -0
- package/dist/audio-room/audio-room.styles.ts +88 -0
- package/dist/audio-room/audio-room.ts +70 -0
- package/dist/audio-room/index.ts +16 -0
- package/dist/call-controls/call-controls.styles.ts +63 -0
- package/dist/call-controls/call-controls.ts +79 -0
- package/dist/call-controls/index.ts +15 -0
- package/dist/checklist/checklist.styles.ts +76 -0
- package/dist/checklist/checklist.ts +62 -0
- package/dist/checklist/index.ts +18 -0
- package/dist/editor-status-bar/editor-status-bar.styles.ts +40 -0
- package/dist/editor-status-bar/editor-status-bar.ts +78 -0
- package/dist/editor-status-bar/index.ts +15 -0
- package/dist/editor-tabs/editor-tabs.styles.ts +52 -0
- package/dist/editor-tabs/editor-tabs.ts +82 -0
- package/dist/editor-tabs/index.ts +15 -0
- package/dist/empty-state/empty-state.styles.ts +42 -0
- package/dist/empty-state/empty-state.ts +27 -0
- package/dist/empty-state/index.ts +14 -0
- package/dist/floating-reactions/floating-reactions.styles.ts +54 -0
- package/dist/floating-reactions/floating-reactions.ts +85 -0
- package/dist/floating-reactions/index.ts +14 -0
- package/dist/flow-editor/flow-editor.styles.ts +56 -0
- package/dist/flow-editor/flow-editor.ts +126 -0
- package/dist/flow-editor/index.ts +18 -0
- package/dist/graph-view/graph-view.styles.ts +66 -0
- package/dist/graph-view/graph-view.ts +130 -0
- package/dist/graph-view/index.ts +21 -0
- package/dist/index.ts +45 -17
- package/dist/infinite-canvas/index.ts +17 -0
- package/dist/infinite-canvas/infinite-canvas.styles.ts +49 -0
- package/dist/infinite-canvas/infinite-canvas.ts +97 -0
- package/dist/input/index.ts +2 -0
- package/dist/input/input.styles.ts +4 -0
- package/dist/input/input.ts +39 -3
- package/dist/live-captions/index.ts +13 -0
- package/dist/live-captions/live-captions.styles.ts +45 -0
- package/dist/live-captions/live-captions.ts +57 -0
- package/dist/live-cursors/index.ts +14 -0
- package/dist/live-cursors/live-cursors.styles.ts +41 -0
- package/dist/live-cursors/live-cursors.ts +73 -0
- package/dist/live-transcript/index.ts +16 -0
- package/dist/live-transcript/live-transcript.styles.ts +48 -0
- package/dist/live-transcript/live-transcript.ts +82 -0
- package/dist/mini-map/index.ts +16 -0
- package/dist/mini-map/mini-map.styles.ts +32 -0
- package/dist/mini-map/mini-map.ts +122 -0
- package/dist/password-input/index.ts +9 -0
- package/dist/password-input/password-input.styles.ts +10 -0
- package/dist/password-input/password-input.ts +45 -0
- package/dist/pre-call-lobby/index.ts +22 -0
- package/dist/pre-call-lobby/pre-call-lobby.styles.ts +108 -0
- package/dist/pre-call-lobby/pre-call-lobby.ts +61 -0
- package/dist/radial-gauge/index.ts +26 -0
- package/dist/radial-gauge/radial-gauge.styles.ts +75 -0
- package/dist/radial-gauge/radial-gauge.ts +183 -0
- package/dist/rating-scale/index.ts +16 -0
- package/dist/rating-scale/rating-scale.styles.ts +48 -0
- package/dist/rating-scale/rating-scale.ts +101 -0
- package/dist/segmented-control/index.ts +13 -0
- package/dist/segmented-control/segmented-control.styles.ts +42 -0
- package/dist/segmented-control/segmented-control.ts +80 -0
- package/dist/separator/index.ts +12 -0
- package/dist/separator/separator.styles.ts +34 -0
- package/dist/separator/separator.ts +53 -0
- package/dist/social-auth-button/index.ts +14 -0
- package/dist/social-auth-button/social-auth-button.styles.ts +16 -0
- package/dist/social-auth-button/social-auth-button.ts +51 -0
- package/dist/sticky-note/index.ts +14 -0
- package/dist/sticky-note/sticky-note.styles.ts +37 -0
- package/dist/sticky-note/sticky-note.ts +47 -0
- package/dist/terminal/index.ts +13 -0
- package/dist/terminal/terminal.styles.ts +56 -0
- package/dist/terminal/terminal.ts +60 -0
- package/dist/test-results/index.ts +19 -0
- package/dist/test-results/test-results.styles.ts +93 -0
- package/dist/test-results/test-results.ts +83 -0
- package/dist/timeline/index.ts +22 -0
- package/dist/timeline/timeline.styles.ts +98 -0
- package/dist/timeline/timeline.ts +86 -0
- package/dist/video-grid/index.ts +16 -0
- package/dist/video-grid/video-grid.styles.ts +38 -0
- package/dist/video-grid/video-grid.ts +93 -0
- package/dist/video-tile/index.ts +17 -0
- package/dist/video-tile/video-tile.styles.ts +59 -0
- package/dist/video-tile/video-tile.ts +74 -0
- package/dist/wizard/index.ts +28 -0
- package/dist/wizard/wizard.styles.ts +147 -0
- package/dist/wizard/wizard.ts +128 -0
- package/package.json +47 -19
|
@@ -0,0 +1,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
|
+
})
|