@refraction-ui/astro 0.12.0 → 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-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-mini-map/MiniMap.astro +74 -0
- package/dist/astro-mini-map/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-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/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 +23 -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/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/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/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 +24 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
import {
|
|
3
|
+
createAudioRoom,
|
|
4
|
+
getInitials,
|
|
5
|
+
orbColumns,
|
|
6
|
+
audioRoomVariants,
|
|
7
|
+
audioRoomOrbCellVariants,
|
|
8
|
+
speakingOrbVariants,
|
|
9
|
+
audioRoomNameLabelClass,
|
|
10
|
+
audioRoomMutedBadgeClass,
|
|
11
|
+
audioRoomHandBadgeClass,
|
|
12
|
+
type AudioParticipant,
|
|
13
|
+
} from '../audio-room/index.ts'
|
|
14
|
+
import { cn } from '../shared/index.ts'
|
|
15
|
+
|
|
16
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
17
|
+
/** Participants to render as speaking orbs. */
|
|
18
|
+
participants: AudioParticipant[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { participants, class: className, ...rest } = Astro.props
|
|
22
|
+
|
|
23
|
+
const cols = orbColumns(participants.length)
|
|
24
|
+
const { ariaProps, dataAttributes } = createAudioRoom()
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<div
|
|
28
|
+
class={cn(audioRoomVariants(), className)}
|
|
29
|
+
style={`grid-template-columns: repeat(${cols}, minmax(0, 1fr))`}
|
|
30
|
+
{...ariaProps}
|
|
31
|
+
{...dataAttributes}
|
|
32
|
+
{...rest}
|
|
33
|
+
>
|
|
34
|
+
{
|
|
35
|
+
participants.map((participant) => {
|
|
36
|
+
const initials = getInitials(participant.name)
|
|
37
|
+
const speaking = participant.speaking ?? false
|
|
38
|
+
const muted = participant.muted ?? false
|
|
39
|
+
const handRaised = participant.handRaised ?? false
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div class={audioRoomOrbCellVariants()}>
|
|
43
|
+
<div
|
|
44
|
+
role="img"
|
|
45
|
+
aria-label={participant.name}
|
|
46
|
+
data-speaking={speaking ? 'true' : 'false'}
|
|
47
|
+
data-muted={muted ? 'true' : 'false'}
|
|
48
|
+
data-hand-raised={handRaised ? 'true' : 'false'}
|
|
49
|
+
class={speakingOrbVariants({
|
|
50
|
+
speaking: speaking ? 'true' : 'false',
|
|
51
|
+
muted: muted ? 'true' : 'false',
|
|
52
|
+
})}
|
|
53
|
+
>
|
|
54
|
+
{participant.avatarUrl ? (
|
|
55
|
+
<img
|
|
56
|
+
src={participant.avatarUrl}
|
|
57
|
+
alt={participant.name}
|
|
58
|
+
class="absolute inset-0 size-full object-cover"
|
|
59
|
+
/>
|
|
60
|
+
) : (
|
|
61
|
+
<span aria-hidden="true">{initials}</span>
|
|
62
|
+
)}
|
|
63
|
+
|
|
64
|
+
{muted && (
|
|
65
|
+
<span class={audioRoomMutedBadgeClass} aria-label="Muted">
|
|
66
|
+
🔇
|
|
67
|
+
</span>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
{handRaised && (
|
|
71
|
+
<span class={audioRoomHandBadgeClass} aria-label="Hand raised">
|
|
72
|
+
✋
|
|
73
|
+
</span>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
<span class={audioRoomNameLabelClass}>{participant.name}</span>
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
import {
|
|
3
|
+
createCallControls,
|
|
4
|
+
callControlsVariants,
|
|
5
|
+
callControlButtonVariants,
|
|
6
|
+
callControlLabelClass,
|
|
7
|
+
type CallControlTone,
|
|
8
|
+
type CallControlItem,
|
|
9
|
+
} from '../call-controls/index.ts'
|
|
10
|
+
import { cn } from '../shared/index.ts'
|
|
11
|
+
|
|
12
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
13
|
+
/**
|
|
14
|
+
* Array of control descriptors to render as buttons.
|
|
15
|
+
* If omitted, the default <slot /> is used instead (composable usage).
|
|
16
|
+
*/
|
|
17
|
+
controls?: CallControlItem[]
|
|
18
|
+
/** Visual size of the toolbar and its buttons. */
|
|
19
|
+
size?: 'sm' | 'md'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
controls,
|
|
24
|
+
size = 'md',
|
|
25
|
+
class: className,
|
|
26
|
+
...rest
|
|
27
|
+
} = Astro.props
|
|
28
|
+
|
|
29
|
+
const { ariaProps, dataAttributes } = createCallControls({ controls })
|
|
30
|
+
|
|
31
|
+
function toneClass(tone: CallControlTone | undefined): string {
|
|
32
|
+
return callControlButtonVariants({ tone: tone ?? 'default', size })
|
|
33
|
+
}
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
<div
|
|
37
|
+
class={cn(callControlsVariants({ size }), className)}
|
|
38
|
+
role={ariaProps.role}
|
|
39
|
+
aria-label={ariaProps['aria-label']}
|
|
40
|
+
{...dataAttributes}
|
|
41
|
+
{...rest}
|
|
42
|
+
>
|
|
43
|
+
{controls
|
|
44
|
+
? controls.map((ctrl) => (
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
aria-label={ctrl.label}
|
|
48
|
+
aria-pressed={ctrl.pressed !== undefined ? ctrl.pressed : undefined}
|
|
49
|
+
class={toneClass(ctrl.tone)}
|
|
50
|
+
>
|
|
51
|
+
{ctrl.icon && <span aria-hidden="true">{ctrl.icon}</span>}
|
|
52
|
+
<span class={callControlLabelClass}>{ctrl.label}</span>
|
|
53
|
+
</button>
|
|
54
|
+
))
|
|
55
|
+
: <slot />
|
|
56
|
+
}
|
|
57
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
import {
|
|
3
|
+
createChecklist,
|
|
4
|
+
checklistProgress,
|
|
5
|
+
checklistContainerClass,
|
|
6
|
+
checklistListClass,
|
|
7
|
+
checklistItemVariants,
|
|
8
|
+
checklistBoxVariants,
|
|
9
|
+
checklistLabelWrapClass,
|
|
10
|
+
checklistLabelVariants,
|
|
11
|
+
checklistDescriptionClass,
|
|
12
|
+
checklistProgressClass,
|
|
13
|
+
type ChecklistItemData,
|
|
14
|
+
} from '../checklist/index.ts'
|
|
15
|
+
import { cn } from '../shared/index.ts'
|
|
16
|
+
|
|
17
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
18
|
+
/** List of checklist items. */
|
|
19
|
+
items: ChecklistItemData[]
|
|
20
|
+
/** Show a "{completed}/{total}" progress summary below the list. */
|
|
21
|
+
showProgress?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { items, showProgress = false, class: className, ...rest } = Astro.props
|
|
25
|
+
|
|
26
|
+
const { ariaProps, dataAttributes } = createChecklist()
|
|
27
|
+
const progress = showProgress ? checklistProgress(items) : null
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
<div class={cn(checklistContainerClass, className)} {...dataAttributes} {...rest}>
|
|
31
|
+
<ul class={checklistListClass} {...ariaProps}>
|
|
32
|
+
{
|
|
33
|
+
items.map((item) => {
|
|
34
|
+
const checked = Boolean(item.checked)
|
|
35
|
+
return (
|
|
36
|
+
<li role="presentation">
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
role="checkbox"
|
|
40
|
+
aria-checked={checked}
|
|
41
|
+
data-state={checked ? 'checked' : 'unchecked'}
|
|
42
|
+
class={checklistItemVariants({ checked: checked ? 'true' : 'false' })}
|
|
43
|
+
>
|
|
44
|
+
<span aria-hidden="true" class={checklistBoxVariants({ checked: checked ? 'true' : 'false' })}>
|
|
45
|
+
{checked && (
|
|
46
|
+
<svg viewBox="0 0 12 12" fill="none" class="h-3 w-3" aria-hidden="true">
|
|
47
|
+
<path
|
|
48
|
+
d="M2 6l3 3 5-5"
|
|
49
|
+
stroke="currentColor"
|
|
50
|
+
stroke-width="1.5"
|
|
51
|
+
stroke-linecap="round"
|
|
52
|
+
stroke-linejoin="round"
|
|
53
|
+
/>
|
|
54
|
+
</svg>
|
|
55
|
+
)}
|
|
56
|
+
</span>
|
|
57
|
+
<span class={checklistLabelWrapClass}>
|
|
58
|
+
<span class={checklistLabelVariants({ checked: checked ? 'true' : 'false' })}>{item.label}</span>
|
|
59
|
+
{item.description && (
|
|
60
|
+
<span class={checklistDescriptionClass}>{item.description}</span>
|
|
61
|
+
)}
|
|
62
|
+
</span>
|
|
63
|
+
</button>
|
|
64
|
+
</li>
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
</ul>
|
|
69
|
+
{
|
|
70
|
+
progress !== null && (
|
|
71
|
+
<p class={checklistProgressClass}>
|
|
72
|
+
{progress.completed}/{progress.total} completed
|
|
73
|
+
</p>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
import {
|
|
3
|
+
createEditorStatusBar,
|
|
4
|
+
formatCursorPosition,
|
|
5
|
+
partitionSegments,
|
|
6
|
+
editorStatusBarVariants,
|
|
7
|
+
statusBarGroupClass,
|
|
8
|
+
statusBarSegmentVariants,
|
|
9
|
+
type StatusSegment,
|
|
10
|
+
type StatusSegmentTone,
|
|
11
|
+
} from '../editor-status-bar/index.ts'
|
|
12
|
+
import { cn } from '../shared/index.ts'
|
|
13
|
+
|
|
14
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
15
|
+
/**
|
|
16
|
+
* Explicit list of segments. When provided, all convenience props are
|
|
17
|
+
* ignored.
|
|
18
|
+
*/
|
|
19
|
+
segments?: StatusSegment[]
|
|
20
|
+
/** Current line number (1-indexed). */
|
|
21
|
+
line?: number
|
|
22
|
+
/** Current column number (1-indexed). */
|
|
23
|
+
col?: number
|
|
24
|
+
/** Indentation descriptor, e.g. `'Spaces: 4'`. */
|
|
25
|
+
indentation?: string
|
|
26
|
+
/** Language / runtime label, e.g. `'Python 3.11.4'`. */
|
|
27
|
+
language?: string
|
|
28
|
+
/** File encoding, e.g. `'UTF-8'`. */
|
|
29
|
+
encoding?: string
|
|
30
|
+
/** End-of-line sequence, e.g. `'LF'` or `'CRLF'`. */
|
|
31
|
+
eol?: string
|
|
32
|
+
/** Persistence status label, e.g. `'Auto-saved'`. */
|
|
33
|
+
status?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
segments: segmentsProp,
|
|
38
|
+
line,
|
|
39
|
+
col,
|
|
40
|
+
indentation,
|
|
41
|
+
language,
|
|
42
|
+
encoding,
|
|
43
|
+
eol,
|
|
44
|
+
status,
|
|
45
|
+
class: className,
|
|
46
|
+
...rest
|
|
47
|
+
} = Astro.props
|
|
48
|
+
|
|
49
|
+
function buildSegments(): StatusSegment[] {
|
|
50
|
+
const segs: StatusSegment[] = []
|
|
51
|
+
if (line !== undefined && col !== undefined) {
|
|
52
|
+
segs.push({ id: 'cursor', label: formatCursorPosition(line, col), align: 'left', tone: 'default' })
|
|
53
|
+
}
|
|
54
|
+
if (indentation) {
|
|
55
|
+
segs.push({ id: 'indentation', label: indentation, align: 'left', tone: 'muted' })
|
|
56
|
+
}
|
|
57
|
+
if (language) {
|
|
58
|
+
segs.push({ id: 'language', label: language, align: 'right', tone: 'accent' })
|
|
59
|
+
}
|
|
60
|
+
if (encoding) {
|
|
61
|
+
segs.push({ id: 'encoding', label: encoding, align: 'right', tone: 'muted' })
|
|
62
|
+
}
|
|
63
|
+
if (eol) {
|
|
64
|
+
segs.push({ id: 'eol', label: eol, align: 'right', tone: 'muted' })
|
|
65
|
+
}
|
|
66
|
+
if (status) {
|
|
67
|
+
segs.push({ id: 'status', label: status, align: 'right', tone: 'muted' })
|
|
68
|
+
}
|
|
69
|
+
return segs
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const segments = segmentsProp ?? buildSegments()
|
|
73
|
+
const { left, right } = partitionSegments(segments)
|
|
74
|
+
const { ariaProps, dataAttributes } = createEditorStatusBar()
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
<div class={cn(editorStatusBarVariants(), className)} {...ariaProps} {...dataAttributes} {...rest}>
|
|
78
|
+
<div class={statusBarGroupClass}>
|
|
79
|
+
{left.map((seg) => (
|
|
80
|
+
<span class={statusBarSegmentVariants({ tone: (seg.tone ?? 'default') as StatusSegmentTone })}>
|
|
81
|
+
{seg.label}
|
|
82
|
+
</span>
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
<div class={statusBarGroupClass}>
|
|
86
|
+
{right.map((seg) => (
|
|
87
|
+
<span class={statusBarSegmentVariants({ tone: (seg.tone ?? 'default') as StatusSegmentTone })}>
|
|
88
|
+
{seg.label}
|
|
89
|
+
</span>
|
|
90
|
+
))}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
import {
|
|
3
|
+
createEditorTabs,
|
|
4
|
+
editorTabsVariants,
|
|
5
|
+
editorTabVariants,
|
|
6
|
+
editorTabDirtyDotClass,
|
|
7
|
+
editorTabCloseButtonClass,
|
|
8
|
+
editorTabIconClass,
|
|
9
|
+
type EditorTabData,
|
|
10
|
+
} from '../editor-tabs/index.ts'
|
|
11
|
+
import { cn } from '../shared/index.ts'
|
|
12
|
+
|
|
13
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
14
|
+
/** The list of tab entries to render. */
|
|
15
|
+
tabs: EditorTabData[]
|
|
16
|
+
/** The id of the currently active tab. */
|
|
17
|
+
activeId: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
tabs,
|
|
22
|
+
activeId,
|
|
23
|
+
class: className,
|
|
24
|
+
...rest
|
|
25
|
+
} = Astro.props
|
|
26
|
+
|
|
27
|
+
const activeIndex = tabs.findIndex((t) => t.id === activeId)
|
|
28
|
+
const tabbableIndex = activeIndex === -1 ? 0 : activeIndex
|
|
29
|
+
const { ariaProps, dataAttributes } = createEditorTabs({ activeId })
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
<div class={cn(editorTabsVariants(), className)} {...ariaProps} {...dataAttributes} {...rest}>
|
|
33
|
+
{
|
|
34
|
+
tabs.map((tab, index) => {
|
|
35
|
+
const isActive = tab.id === activeId
|
|
36
|
+
return (
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
role="tab"
|
|
40
|
+
aria-selected={isActive}
|
|
41
|
+
aria-controls={`tabpanel-${tab.id}`}
|
|
42
|
+
id={`tab-${tab.id}`}
|
|
43
|
+
tabindex={index === tabbableIndex ? 0 : -1}
|
|
44
|
+
data-state={isActive ? 'active' : 'inactive'}
|
|
45
|
+
class={editorTabVariants({ state: isActive ? 'active' : 'inactive' })}
|
|
46
|
+
>
|
|
47
|
+
{tab.icon && (
|
|
48
|
+
<span class={editorTabIconClass} aria-hidden="true">
|
|
49
|
+
{tab.icon}
|
|
50
|
+
</span>
|
|
51
|
+
)}
|
|
52
|
+
<span>{tab.label}</span>
|
|
53
|
+
{tab.dirty && (
|
|
54
|
+
<span
|
|
55
|
+
class={editorTabDirtyDotClass}
|
|
56
|
+
aria-label="unsaved changes"
|
|
57
|
+
role="status"
|
|
58
|
+
/>
|
|
59
|
+
)}
|
|
60
|
+
{tab.closable && (
|
|
61
|
+
<span
|
|
62
|
+
role="button"
|
|
63
|
+
aria-label={`Close ${tab.label}`}
|
|
64
|
+
tabindex="-1"
|
|
65
|
+
class={editorTabCloseButtonClass}
|
|
66
|
+
>
|
|
67
|
+
×
|
|
68
|
+
</span>
|
|
69
|
+
)}
|
|
70
|
+
</button>
|
|
71
|
+
)
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
import {
|
|
3
|
+
createFloatingReactions,
|
|
4
|
+
laneOffsetPercent,
|
|
5
|
+
floatingReactionsVariants,
|
|
6
|
+
floatingReactionItemClass,
|
|
7
|
+
floatingReactionLabelClass,
|
|
8
|
+
type FloatingReaction,
|
|
9
|
+
} from '../floating-reactions/index.ts'
|
|
10
|
+
import { cn } from '../shared/index.ts'
|
|
11
|
+
|
|
12
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
13
|
+
/**
|
|
14
|
+
* Reactions to render at SSR time. For animated runtime usage, use the
|
|
15
|
+
* React adapter which manages auto-expiry via useFloatingReactions().
|
|
16
|
+
*/
|
|
17
|
+
reactions?: FloatingReaction[]
|
|
18
|
+
/**
|
|
19
|
+
* Total number of horizontal lane buckets used to spread reactions.
|
|
20
|
+
* @default 5
|
|
21
|
+
*/
|
|
22
|
+
lanes?: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
reactions = [],
|
|
27
|
+
lanes = 5,
|
|
28
|
+
class: className,
|
|
29
|
+
...rest
|
|
30
|
+
} = Astro.props
|
|
31
|
+
|
|
32
|
+
const { ariaProps, dataAttributes } = createFloatingReactions()
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
<div
|
|
36
|
+
class={cn(floatingReactionsVariants({ active: reactions.length > 0 ? 'true' : 'false' }), className)}
|
|
37
|
+
{...ariaProps}
|
|
38
|
+
{...dataAttributes}
|
|
39
|
+
{...rest}
|
|
40
|
+
>
|
|
41
|
+
{
|
|
42
|
+
reactions.map((reaction) => {
|
|
43
|
+
const left = laneOffsetPercent(reaction.lane ?? 0, lanes)
|
|
44
|
+
return (
|
|
45
|
+
<span
|
|
46
|
+
class={floatingReactionItemClass}
|
|
47
|
+
style={`left:${left}%`}
|
|
48
|
+
aria-hidden="true"
|
|
49
|
+
>
|
|
50
|
+
{reaction.emoji}
|
|
51
|
+
<span class={floatingReactionLabelClass}>{reaction.emoji}</span>
|
|
52
|
+
</span>
|
|
53
|
+
)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
import {
|
|
3
|
+
createFlowEditor,
|
|
4
|
+
edgePath,
|
|
5
|
+
nodeById,
|
|
6
|
+
flowEditorVariants,
|
|
7
|
+
flowNodeVariants,
|
|
8
|
+
flowNodeLabelClass,
|
|
9
|
+
flowEdgePathClass,
|
|
10
|
+
flowEdgeLabelClass,
|
|
11
|
+
type FlowNode,
|
|
12
|
+
type FlowEdge,
|
|
13
|
+
} from '../flow-editor/index.ts'
|
|
14
|
+
import { cn } from '../shared/index.ts'
|
|
15
|
+
|
|
16
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
17
|
+
/** The nodes to render. */
|
|
18
|
+
nodes: FlowNode[]
|
|
19
|
+
/** The directed edges connecting nodes. */
|
|
20
|
+
edges: FlowEdge[]
|
|
21
|
+
/** Id of the selected node. */
|
|
22
|
+
selectedId?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
nodes,
|
|
27
|
+
edges,
|
|
28
|
+
selectedId,
|
|
29
|
+
class: className,
|
|
30
|
+
...rest
|
|
31
|
+
} = Astro.props
|
|
32
|
+
|
|
33
|
+
const { ariaProps, dataAttributes } = createFlowEditor()
|
|
34
|
+
|
|
35
|
+
const DEFAULT_W = 160
|
|
36
|
+
const DEFAULT_H = 48
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
<!--
|
|
40
|
+
Astro renders a static snapshot of the graph. The outer div uses a relative
|
|
41
|
+
bg-grid container (no InfiniteCanvas import — the .astro canvas is not trivial
|
|
42
|
+
to SSR without its own island machinery). Pan/zoom is a React-only feature.
|
|
43
|
+
-->
|
|
44
|
+
<div
|
|
45
|
+
class={cn(flowEditorVariants(), 'bg-[size:24px_24px] bg-[image:linear-gradient(to_right,hsl(var(--border))_1px,transparent_1px),linear-gradient(to_bottom,hsl(var(--border))_1px,transparent_1px)]', className)}
|
|
46
|
+
{...ariaProps}
|
|
47
|
+
{...dataAttributes}
|
|
48
|
+
{...rest}
|
|
49
|
+
>
|
|
50
|
+
<!-- SVG edge layer -->
|
|
51
|
+
<svg
|
|
52
|
+
class="absolute inset-0 w-full h-full overflow-visible text-muted-foreground pointer-events-none"
|
|
53
|
+
aria-hidden="true"
|
|
54
|
+
>
|
|
55
|
+
{
|
|
56
|
+
edges.map((edge) => {
|
|
57
|
+
const src = nodeById(nodes, edge.source)
|
|
58
|
+
const tgt = nodeById(nodes, edge.target)
|
|
59
|
+
if (!src || !tgt) return null
|
|
60
|
+
const d = edgePath(src, tgt)
|
|
61
|
+
const midX = (src.x + (src.width ?? DEFAULT_W) + tgt.x) / 2
|
|
62
|
+
const midY = (src.y + (src.height ?? DEFAULT_H) / 2 + tgt.y + (tgt.height ?? DEFAULT_H) / 2) / 2
|
|
63
|
+
return (
|
|
64
|
+
<g>
|
|
65
|
+
<path d={d} class={flowEdgePathClass} />
|
|
66
|
+
{edge.label != null && (
|
|
67
|
+
<text
|
|
68
|
+
x={midX}
|
|
69
|
+
y={midY - 4}
|
|
70
|
+
text-anchor="middle"
|
|
71
|
+
class={flowEdgeLabelClass}
|
|
72
|
+
>
|
|
73
|
+
{edge.label}
|
|
74
|
+
</text>
|
|
75
|
+
)}
|
|
76
|
+
</g>
|
|
77
|
+
)
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
</svg>
|
|
81
|
+
|
|
82
|
+
<!-- Node layer -->
|
|
83
|
+
{
|
|
84
|
+
nodes.map((node) => {
|
|
85
|
+
const isSelected = node.selected || node.id === selectedId
|
|
86
|
+
const w = node.width ?? DEFAULT_W
|
|
87
|
+
const h = node.height ?? DEFAULT_H
|
|
88
|
+
return (
|
|
89
|
+
<div
|
|
90
|
+
class={cn(flowNodeVariants({ selected: isSelected ? 'true' : 'false' }))}
|
|
91
|
+
style={`left:${node.x}px;top:${node.y}px;width:${w}px;height:${h}px`}
|
|
92
|
+
aria-label={node.label}
|
|
93
|
+
>
|
|
94
|
+
<span class={flowNodeLabelClass}>{node.label}</span>
|
|
95
|
+
</div>
|
|
96
|
+
)
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
</div>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
import {
|
|
3
|
+
createGraphView,
|
|
4
|
+
graphEdgePath,
|
|
5
|
+
summarizeStates,
|
|
6
|
+
graphViewVariants,
|
|
7
|
+
graphNodeVariants,
|
|
8
|
+
graphNodeLabelClass,
|
|
9
|
+
graphEdgeClass,
|
|
10
|
+
graphLegendChipVariants,
|
|
11
|
+
type GraphNode,
|
|
12
|
+
type GraphEdge,
|
|
13
|
+
type GraphNodeState,
|
|
14
|
+
} from '../graph-view/index.ts'
|
|
15
|
+
import { cn } from '../shared/index.ts'
|
|
16
|
+
|
|
17
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
18
|
+
/** Nodes to render. */
|
|
19
|
+
nodes: GraphNode[]
|
|
20
|
+
/** Directed edges connecting nodes. */
|
|
21
|
+
edges: GraphEdge[]
|
|
22
|
+
/** Show a legend summarising mastery-state counts. */
|
|
23
|
+
showLegend?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { nodes, edges, showLegend = false, class: className, ...rest } = Astro.props
|
|
27
|
+
|
|
28
|
+
const api = createGraphView()
|
|
29
|
+
const summary = summarizeStates(nodes)
|
|
30
|
+
|
|
31
|
+
const STATE_LABELS: Record<GraphNodeState, string> = {
|
|
32
|
+
mastered: 'Mastered',
|
|
33
|
+
'in-progress': 'In Progress',
|
|
34
|
+
'not-started': 'Not Started',
|
|
35
|
+
highlight: 'Highlight',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const LEGEND_ORDER: GraphNodeState[] = ['mastered', 'in-progress', 'not-started', 'highlight']
|
|
39
|
+
|
|
40
|
+
const padding = 80
|
|
41
|
+
const xs = nodes.map((n) => n.x)
|
|
42
|
+
const ys = nodes.map((n) => n.y)
|
|
43
|
+
const svgWidth = nodes.length === 0 ? 400 : Math.max(...xs) + padding * 2
|
|
44
|
+
const svgHeight = nodes.length === 0 ? 300 : Math.max(...ys) + padding * 2
|
|
45
|
+
|
|
46
|
+
// Build node lookup for edge resolution
|
|
47
|
+
const nodeMap = new Map<string, GraphNode>(nodes.map((n) => [n.id, n]))
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
<div
|
|
51
|
+
class={cn(graphViewVariants(), className)}
|
|
52
|
+
{...api.ariaProps}
|
|
53
|
+
{...api.dataAttributes}
|
|
54
|
+
{...rest}
|
|
55
|
+
>
|
|
56
|
+
<div class="relative" style={`width:${svgWidth}px;height:${svgHeight}px;`}>
|
|
57
|
+
<!-- SVG edge layer -->
|
|
58
|
+
<svg
|
|
59
|
+
width={svgWidth}
|
|
60
|
+
height={svgHeight}
|
|
61
|
+
aria-hidden="true"
|
|
62
|
+
style="position:absolute;top:0;left:0;pointer-events:none;"
|
|
63
|
+
>
|
|
64
|
+
{
|
|
65
|
+
edges.map((edge) => {
|
|
66
|
+
const source = nodeMap.get(edge.source)
|
|
67
|
+
const target = nodeMap.get(edge.target)
|
|
68
|
+
if (!source || !target) return null
|
|
69
|
+
return <path d={graphEdgePath(source, target)} class={graphEdgeClass} />
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
</svg>
|
|
73
|
+
|
|
74
|
+
<!-- Node chips -->
|
|
75
|
+
{
|
|
76
|
+
nodes.map((node) => {
|
|
77
|
+
const state: GraphNodeState = node.state ?? 'not-started'
|
|
78
|
+
return (
|
|
79
|
+
<span
|
|
80
|
+
data-state={state}
|
|
81
|
+
aria-label={node.label}
|
|
82
|
+
class={graphNodeVariants({ state, interactive: 'false' })}
|
|
83
|
+
style={`left:${node.x}px;top:${node.y}px;`}
|
|
84
|
+
>
|
|
85
|
+
<span class={graphNodeLabelClass}>{node.label}</span>
|
|
86
|
+
</span>
|
|
87
|
+
)
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<!-- Optional legend -->
|
|
93
|
+
{
|
|
94
|
+
showLegend && (
|
|
95
|
+
<div
|
|
96
|
+
role="list"
|
|
97
|
+
aria-label="Graph legend"
|
|
98
|
+
class="flex flex-wrap gap-1.5 p-3"
|
|
99
|
+
>
|
|
100
|
+
{LEGEND_ORDER.filter((s) => summary[s] > 0).map((s) => (
|
|
101
|
+
<span role="listitem" class={graphLegendChipVariants({ state: s })}>
|
|
102
|
+
{STATE_LABELS[s]}
|
|
103
|
+
<span class="opacity-70">{summary[s]}</span>
|
|
104
|
+
</span>
|
|
105
|
+
))}
|
|
106
|
+
</div>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|