@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.
Files changed (117) 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-floating-reactions/FloatingReactions.astro +56 -0
  12. package/dist/astro-floating-reactions/index.ts +1 -0
  13. package/dist/astro-flow-editor/FlowEditor.astro +99 -0
  14. package/dist/astro-flow-editor/index.ts +3 -0
  15. package/dist/astro-graph-view/GraphView.astro +109 -0
  16. package/dist/astro-graph-view/index.ts +1 -0
  17. package/dist/astro-infinite-canvas/InfiniteCanvas.astro +43 -0
  18. package/dist/astro-infinite-canvas/index.ts +1 -0
  19. package/dist/astro-live-captions/LiveCaptions.astro +54 -0
  20. package/dist/astro-live-captions/index.ts +1 -0
  21. package/dist/astro-live-cursors/LiveCursors.astro +55 -0
  22. package/dist/astro-live-cursors/index.ts +1 -0
  23. package/dist/astro-live-transcript/LiveTranscript.astro +55 -0
  24. package/dist/astro-live-transcript/index.ts +1 -0
  25. package/dist/astro-mini-map/MiniMap.astro +74 -0
  26. package/dist/astro-mini-map/index.ts +1 -0
  27. package/dist/astro-pre-call-lobby/PreCallLobby.astro +202 -0
  28. package/dist/astro-pre-call-lobby/index.ts +1 -0
  29. package/dist/astro-radial-gauge/RadialGauge.astro +144 -0
  30. package/dist/astro-radial-gauge/index.ts +1 -0
  31. package/dist/astro-rating-scale/RatingScale.astro +73 -0
  32. package/dist/astro-rating-scale/index.ts +1 -0
  33. package/dist/astro-sticky-note/StickyNote.astro +55 -0
  34. package/dist/astro-sticky-note/index.ts +1 -0
  35. package/dist/astro-terminal/Terminal.astro +67 -0
  36. package/dist/astro-terminal/index.ts +1 -0
  37. package/dist/astro-test-results/TestResults.astro +76 -0
  38. package/dist/astro-test-results/index.ts +1 -0
  39. package/dist/astro-timeline/Timeline.astro +59 -0
  40. package/dist/astro-timeline/index.ts +1 -0
  41. package/dist/astro-video-grid/VideoGrid.astro +182 -0
  42. package/dist/astro-video-grid/index.ts +1 -0
  43. package/dist/astro-video-tile/VideoTile.astro +104 -0
  44. package/dist/astro-video-tile/index.ts +1 -0
  45. package/dist/astro-wizard/Wizard.astro +137 -0
  46. package/dist/astro-wizard/index.ts +1 -0
  47. package/dist/audio-room/audio-room.styles.ts +88 -0
  48. package/dist/audio-room/audio-room.ts +70 -0
  49. package/dist/audio-room/index.ts +16 -0
  50. package/dist/call-controls/call-controls.styles.ts +63 -0
  51. package/dist/call-controls/call-controls.ts +79 -0
  52. package/dist/call-controls/index.ts +15 -0
  53. package/dist/checklist/checklist.styles.ts +76 -0
  54. package/dist/checklist/checklist.ts +62 -0
  55. package/dist/checklist/index.ts +18 -0
  56. package/dist/editor-status-bar/editor-status-bar.styles.ts +40 -0
  57. package/dist/editor-status-bar/editor-status-bar.ts +78 -0
  58. package/dist/editor-status-bar/index.ts +15 -0
  59. package/dist/editor-tabs/editor-tabs.styles.ts +52 -0
  60. package/dist/editor-tabs/editor-tabs.ts +82 -0
  61. package/dist/editor-tabs/index.ts +15 -0
  62. package/dist/floating-reactions/floating-reactions.styles.ts +54 -0
  63. package/dist/floating-reactions/floating-reactions.ts +85 -0
  64. package/dist/floating-reactions/index.ts +14 -0
  65. package/dist/flow-editor/flow-editor.styles.ts +56 -0
  66. package/dist/flow-editor/flow-editor.ts +126 -0
  67. package/dist/flow-editor/index.ts +18 -0
  68. package/dist/graph-view/graph-view.styles.ts +66 -0
  69. package/dist/graph-view/graph-view.ts +130 -0
  70. package/dist/graph-view/index.ts +21 -0
  71. package/dist/index.ts +23 -0
  72. package/dist/infinite-canvas/index.ts +17 -0
  73. package/dist/infinite-canvas/infinite-canvas.styles.ts +49 -0
  74. package/dist/infinite-canvas/infinite-canvas.ts +97 -0
  75. package/dist/live-captions/index.ts +13 -0
  76. package/dist/live-captions/live-captions.styles.ts +45 -0
  77. package/dist/live-captions/live-captions.ts +57 -0
  78. package/dist/live-cursors/index.ts +14 -0
  79. package/dist/live-cursors/live-cursors.styles.ts +41 -0
  80. package/dist/live-cursors/live-cursors.ts +73 -0
  81. package/dist/live-transcript/index.ts +16 -0
  82. package/dist/live-transcript/live-transcript.styles.ts +48 -0
  83. package/dist/live-transcript/live-transcript.ts +82 -0
  84. package/dist/mini-map/index.ts +16 -0
  85. package/dist/mini-map/mini-map.styles.ts +32 -0
  86. package/dist/mini-map/mini-map.ts +122 -0
  87. package/dist/pre-call-lobby/index.ts +22 -0
  88. package/dist/pre-call-lobby/pre-call-lobby.styles.ts +108 -0
  89. package/dist/pre-call-lobby/pre-call-lobby.ts +61 -0
  90. package/dist/radial-gauge/index.ts +26 -0
  91. package/dist/radial-gauge/radial-gauge.styles.ts +75 -0
  92. package/dist/radial-gauge/radial-gauge.ts +183 -0
  93. package/dist/rating-scale/index.ts +16 -0
  94. package/dist/rating-scale/rating-scale.styles.ts +48 -0
  95. package/dist/rating-scale/rating-scale.ts +101 -0
  96. package/dist/sticky-note/index.ts +14 -0
  97. package/dist/sticky-note/sticky-note.styles.ts +37 -0
  98. package/dist/sticky-note/sticky-note.ts +47 -0
  99. package/dist/terminal/index.ts +13 -0
  100. package/dist/terminal/terminal.styles.ts +56 -0
  101. package/dist/terminal/terminal.ts +60 -0
  102. package/dist/test-results/index.ts +19 -0
  103. package/dist/test-results/test-results.styles.ts +93 -0
  104. package/dist/test-results/test-results.ts +83 -0
  105. package/dist/timeline/index.ts +22 -0
  106. package/dist/timeline/timeline.styles.ts +98 -0
  107. package/dist/timeline/timeline.ts +86 -0
  108. package/dist/video-grid/index.ts +16 -0
  109. package/dist/video-grid/video-grid.styles.ts +38 -0
  110. package/dist/video-grid/video-grid.ts +93 -0
  111. package/dist/video-tile/index.ts +17 -0
  112. package/dist/video-tile/video-tile.styles.ts +59 -0
  113. package/dist/video-tile/video-tile.ts +74 -0
  114. package/dist/wizard/index.ts +28 -0
  115. package/dist/wizard/wizard.styles.ts +147 -0
  116. package/dist/wizard/wizard.ts +128 -0
  117. 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,3 @@
1
+ // Astro components are imported directly as .astro files; this barrel is a
2
+ // conventional entry-point placeholder for the package.
3
+ export {}
@@ -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 {}