@refraction-ui/astro 0.12.0 → 0.14.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-audience-feature-card/AudienceFeatureCard.astro +53 -0
- package/dist/astro-audience-feature-card/index.ts +1 -0
- package/dist/astro-audio-room/AudioRoom.astro +81 -0
- package/dist/astro-audio-room/index.ts +1 -0
- package/dist/astro-brand-network-cell/BrandNetworkCell.astro +77 -0
- package/dist/astro-brand-network-cell/index.ts +1 -0
- package/dist/astro-browser-chrome-mock/BrowserChromeMock.astro +62 -0
- package/dist/astro-browser-chrome-mock/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-marquee-strip/MarqueeStrip.astro +63 -0
- package/dist/astro-marquee-strip/index.ts +1 -0
- package/dist/astro-mastery-bar/MasteryBar.astro +56 -0
- package/dist/astro-mastery-bar/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-numbered-steps/NumberedSteps.astro +47 -0
- package/dist/astro-numbered-steps/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-pricing-card/PricingCard.astro +114 -0
- package/dist/astro-pricing-card/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-section-head/SectionHead.astro +44 -0
- package/dist/astro-section-head/index.ts +1 -0
- package/dist/astro-stat-grid/StatGrid.astro +46 -0
- package/dist/astro-stat-grid/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/audience-feature-card/audience-feature-card.styles.ts +23 -0
- package/dist/audience-feature-card/audience-feature-card.ts +39 -0
- package/dist/audience-feature-card/index.ts +13 -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/brand-network-cell/brand-network-cell.styles.ts +46 -0
- package/dist/brand-network-cell/brand-network-cell.ts +35 -0
- package/dist/brand-network-cell/index.ts +14 -0
- package/dist/browser-chrome-mock/browser-chrome-mock.styles.ts +43 -0
- package/dist/browser-chrome-mock/browser-chrome-mock.ts +52 -0
- package/dist/browser-chrome-mock/index.ts +17 -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 +32 -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/marquee-strip/index.ts +14 -0
- package/dist/marquee-strip/marquee-strip.styles.ts +61 -0
- package/dist/marquee-strip/marquee-strip.ts +35 -0
- package/dist/mastery-bar/index.ts +15 -0
- package/dist/mastery-bar/mastery-bar.styles.ts +45 -0
- package/dist/mastery-bar/mastery-bar.ts +55 -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/numbered-steps/index.ts +16 -0
- package/dist/numbered-steps/numbered-steps.styles.ts +36 -0
- package/dist/numbered-steps/numbered-steps.ts +64 -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/pricing-card/index.ts +19 -0
- package/dist/pricing-card/pricing-card.styles.ts +71 -0
- package/dist/pricing-card/pricing-card.ts +37 -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/section-head/index.ts +13 -0
- package/dist/section-head/section-head.styles.ts +30 -0
- package/dist/section-head/section-head.ts +28 -0
- package/dist/stat-grid/index.ts +13 -0
- package/dist/stat-grid/stat-grid.styles.ts +25 -0
- package/dist/stat-grid/stat-grid.ts +41 -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 +33 -1
|
@@ -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
|
+
})
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/** Horizontal alignment of a segment within the bar. */
|
|
4
|
+
export type StatusSegmentAlign = 'left' | 'right'
|
|
5
|
+
|
|
6
|
+
/** Visual tone / emphasis of a segment. */
|
|
7
|
+
export type StatusSegmentTone = 'default' | 'muted' | 'accent'
|
|
8
|
+
|
|
9
|
+
/** A single labelled item rendered in the status bar. */
|
|
10
|
+
export interface StatusSegment {
|
|
11
|
+
/** Unique identifier for the segment. */
|
|
12
|
+
id: string
|
|
13
|
+
/** Text displayed in the segment. */
|
|
14
|
+
label: string
|
|
15
|
+
/** Which side of the bar this segment lives on. Defaults to `'left'`. */
|
|
16
|
+
align?: StatusSegmentAlign
|
|
17
|
+
/** Visual tone. Defaults to `'default'`. */
|
|
18
|
+
tone?: StatusSegmentTone
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Return value of {@link createEditorStatusBar}. */
|
|
22
|
+
export interface EditorStatusBarAPI {
|
|
23
|
+
/** ARIA attributes to spread on the bar element. */
|
|
24
|
+
ariaProps: Partial<AccessibilityProps>
|
|
25
|
+
/** Data attributes for styling hooks. */
|
|
26
|
+
dataAttributes: Record<string, string>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Format a cursor position as the canonical IDE string "Ln {line}, Col {col}".
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* formatCursorPosition(17, 1) // → 'Ln 17, Col 1'
|
|
34
|
+
*/
|
|
35
|
+
export function formatCursorPosition(line: number, col: number): string {
|
|
36
|
+
return `Ln ${line}, Col ${col}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Partition a flat list of segments into left- and right-aligned groups,
|
|
41
|
+
* preserving insertion order within each group.
|
|
42
|
+
*/
|
|
43
|
+
export function partitionSegments(segments: StatusSegment[]): {
|
|
44
|
+
left: StatusSegment[]
|
|
45
|
+
right: StatusSegment[]
|
|
46
|
+
} {
|
|
47
|
+
const left: StatusSegment[] = []
|
|
48
|
+
const right: StatusSegment[] = []
|
|
49
|
+
for (const seg of segments) {
|
|
50
|
+
if (seg.align === 'right') {
|
|
51
|
+
right.push(seg)
|
|
52
|
+
} else {
|
|
53
|
+
left.push(seg)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { left, right }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build the framework-agnostic ARIA and data attributes for the status bar
|
|
61
|
+
* container.
|
|
62
|
+
*
|
|
63
|
+
* The bar uses `role="status"` with `aria-live="polite"` so screen readers
|
|
64
|
+
* announce updates (e.g. cursor position changes) without interrupting the
|
|
65
|
+
* user's current focus.
|
|
66
|
+
*/
|
|
67
|
+
export function createEditorStatusBar(): EditorStatusBarAPI {
|
|
68
|
+
const ariaProps: Partial<AccessibilityProps> = {
|
|
69
|
+
role: 'status',
|
|
70
|
+
'aria-live': 'polite',
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const dataAttributes: Record<string, string> = {
|
|
74
|
+
'data-component': 'editor-status-bar',
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { ariaProps, dataAttributes }
|
|
78
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
formatCursorPosition,
|
|
3
|
+
partitionSegments,
|
|
4
|
+
createEditorStatusBar,
|
|
5
|
+
type StatusSegmentAlign,
|
|
6
|
+
type StatusSegmentTone,
|
|
7
|
+
type StatusSegment,
|
|
8
|
+
type EditorStatusBarAPI,
|
|
9
|
+
} from './editor-status-bar.js'
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
editorStatusBarVariants,
|
|
13
|
+
statusBarGroupClass,
|
|
14
|
+
statusBarSegmentVariants,
|
|
15
|
+
} from './editor-status-bar.styles.js'
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/** The tab bar container row. */
|
|
4
|
+
export const editorTabsVariants = cva({
|
|
5
|
+
base: 'inline-flex items-end gap-0 border-b border-border bg-muted/40 overflow-x-auto',
|
|
6
|
+
variants: {},
|
|
7
|
+
defaultVariants: {},
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A single tab button. The `state` variant switches between active and
|
|
12
|
+
* inactive treatments so adapters never inline these classes.
|
|
13
|
+
*/
|
|
14
|
+
export const editorTabVariants = cva({
|
|
15
|
+
base: [
|
|
16
|
+
'group inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium',
|
|
17
|
+
'border-b-2 transition-colors select-none whitespace-nowrap',
|
|
18
|
+
'focus-visible:outline-none focus-visible:ring-2',
|
|
19
|
+
'focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background',
|
|
20
|
+
].join(' '),
|
|
21
|
+
variants: {
|
|
22
|
+
state: {
|
|
23
|
+
active: [
|
|
24
|
+
'border-primary text-foreground bg-background',
|
|
25
|
+
'border-t border-l border-r border-border border-b-background -mb-px',
|
|
26
|
+
].join(' '),
|
|
27
|
+
inactive: [
|
|
28
|
+
'border-transparent text-muted-foreground',
|
|
29
|
+
'hover:text-foreground hover:bg-background/60',
|
|
30
|
+
].join(' '),
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
state: 'inactive',
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
/** The dot indicator shown when a tab has unsaved changes. */
|
|
39
|
+
export const editorTabDirtyDotClass =
|
|
40
|
+
'size-1.5 rounded-full bg-amber-400 shrink-0'
|
|
41
|
+
|
|
42
|
+
/** The close button inside a tab. */
|
|
43
|
+
export const editorTabCloseButtonClass = [
|
|
44
|
+
'ml-1 inline-flex items-center justify-center rounded-sm size-4 shrink-0',
|
|
45
|
+
'text-muted-foreground opacity-0 transition-opacity',
|
|
46
|
+
'group-hover:opacity-100 focus-visible:opacity-100',
|
|
47
|
+
'hover:bg-accent hover:text-foreground',
|
|
48
|
+
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
|
|
49
|
+
].join(' ')
|
|
50
|
+
|
|
51
|
+
/** The optional icon slot to the left of the label. */
|
|
52
|
+
export const editorTabIconClass = 'shrink-0 text-[0.85em] leading-none'
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/** A single tab entry in the editor tab bar. */
|
|
4
|
+
export interface EditorTabData {
|
|
5
|
+
/** Unique identifier for the tab. */
|
|
6
|
+
id: string
|
|
7
|
+
/** Display label (e.g. "solution.py"). */
|
|
8
|
+
label: string
|
|
9
|
+
/** Optional icon — a short string like an emoji or a ligature glyph. */
|
|
10
|
+
icon?: string
|
|
11
|
+
/** Whether the tab has unsaved changes (shows a dirty dot). */
|
|
12
|
+
dirty?: boolean
|
|
13
|
+
/** Whether the tab shows a close button. */
|
|
14
|
+
closable?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface EditorTabsProps {
|
|
18
|
+
/** The id of the currently active tab. */
|
|
19
|
+
activeId?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface EditorTabsAPI {
|
|
23
|
+
/** ARIA attributes to spread on the tab list container. */
|
|
24
|
+
ariaProps: Partial<AccessibilityProps>
|
|
25
|
+
/** Data attributes for styling hooks. */
|
|
26
|
+
dataAttributes: Record<string, string>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build the framework-agnostic container props for an IDE-style editor tab bar.
|
|
31
|
+
*
|
|
32
|
+
* Returns `role="tablist"` plus data attributes. Each individual tab is
|
|
33
|
+
* `role="tab"` with `aria-selected`; adapters spread ariaProps on the container
|
|
34
|
+
* and render the tabs themselves.
|
|
35
|
+
*/
|
|
36
|
+
export function createEditorTabs(props: EditorTabsProps = {}): EditorTabsAPI {
|
|
37
|
+
const { activeId } = props
|
|
38
|
+
|
|
39
|
+
const ariaProps: Partial<AccessibilityProps> = {
|
|
40
|
+
role: 'tablist',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const dataAttributes: Record<string, string> = {}
|
|
44
|
+
if (activeId !== undefined) {
|
|
45
|
+
dataAttributes['data-active-id'] = activeId
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { ariaProps, dataAttributes }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Roving keyboard navigation for the editor tab bar.
|
|
53
|
+
*
|
|
54
|
+
* Unlike a rating scale, tabs **wrap** at the ends so you can cycle through
|
|
55
|
+
* them continuously — matching standard tab-bar behaviour (similar to
|
|
56
|
+
* segmented-control). Home/End jump to the first/last tab.
|
|
57
|
+
*
|
|
58
|
+
* - ArrowRight → next (wraps to first after last)
|
|
59
|
+
* - ArrowLeft → previous (wraps to last before first)
|
|
60
|
+
* - Home → first
|
|
61
|
+
* - End → last
|
|
62
|
+
*/
|
|
63
|
+
export function getNextTabIndex(
|
|
64
|
+
currentIndex: number,
|
|
65
|
+
key: string,
|
|
66
|
+
count: number,
|
|
67
|
+
): number {
|
|
68
|
+
if (count <= 0) return currentIndex
|
|
69
|
+
|
|
70
|
+
switch (key) {
|
|
71
|
+
case 'ArrowRight':
|
|
72
|
+
return (currentIndex + 1) % count
|
|
73
|
+
case 'ArrowLeft':
|
|
74
|
+
return (currentIndex - 1 + count) % count
|
|
75
|
+
case 'Home':
|
|
76
|
+
return 0
|
|
77
|
+
case 'End':
|
|
78
|
+
return count - 1
|
|
79
|
+
default:
|
|
80
|
+
return currentIndex
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createEditorTabs,
|
|
3
|
+
getNextTabIndex,
|
|
4
|
+
type EditorTabData,
|
|
5
|
+
type EditorTabsProps,
|
|
6
|
+
type EditorTabsAPI,
|
|
7
|
+
} from './editor-tabs.js'
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
editorTabsVariants,
|
|
11
|
+
editorTabVariants,
|
|
12
|
+
editorTabDirtyDotClass,
|
|
13
|
+
editorTabCloseButtonClass,
|
|
14
|
+
editorTabIconClass,
|
|
15
|
+
} from './editor-tabs.styles.js'
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Overlay container — fills the parent with absolute positioning so reactions
|
|
5
|
+
* float over content. pointer-events-none keeps the overlay non-interactive;
|
|
6
|
+
* overflow-hidden clips reactions that drift outside the bounds.
|
|
7
|
+
*/
|
|
8
|
+
export const floatingReactionsVariants = cva({
|
|
9
|
+
base: 'absolute inset-0 pointer-events-none overflow-hidden',
|
|
10
|
+
variants: {
|
|
11
|
+
/**
|
|
12
|
+
* Whether the overlay is currently active (has reactions). The parent may
|
|
13
|
+
* toggle visibility, but we keep the node mounted to preserve live-region
|
|
14
|
+
* semantics.
|
|
15
|
+
*/
|
|
16
|
+
active: {
|
|
17
|
+
true: '',
|
|
18
|
+
false: '',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
active: 'false',
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A single floating reaction item.
|
|
28
|
+
*
|
|
29
|
+
* The float-up animation is driven by the class
|
|
30
|
+
* `animate-[float-up_3s_ease-out_forwards]`. Consumers must supply the
|
|
31
|
+
* `@keyframes float-up` rule in their global CSS or Tailwind config:
|
|
32
|
+
*
|
|
33
|
+
* ```css
|
|
34
|
+
* @keyframes float-up {
|
|
35
|
+
* 0% { transform: translateY(0); opacity: 1; }
|
|
36
|
+
* 80% { opacity: 1; }
|
|
37
|
+
* 100% { transform: translateY(-80%); opacity: 0; }
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* Without the keyframe the item will still render in place and fade on removal
|
|
42
|
+
* (graceful degradation).
|
|
43
|
+
*/
|
|
44
|
+
export const floatingReactionItemClass = [
|
|
45
|
+
'absolute bottom-4',
|
|
46
|
+
'text-3xl leading-none select-none',
|
|
47
|
+
'animate-[float-up_3s_ease-out_forwards]',
|
|
48
|
+
].join(' ')
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Accessible label node for the reaction — visually hidden so screen readers
|
|
52
|
+
* read the emoji description once and don't repeat the decorative glyph.
|
|
53
|
+
*/
|
|
54
|
+
export const floatingReactionLabelClass = 'sr-only'
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless core for floating emoji reactions.
|
|
3
|
+
*
|
|
4
|
+
* An emoji burst overlay where reactions animate upward and fade — distinct
|
|
5
|
+
* from the static reaction-bar (which shows counts). This module is JSX-free
|
|
6
|
+
* and framework-agnostic; adapters consume it and manage timing with
|
|
7
|
+
* REACTION_LIFETIME_MS.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** A single transient reaction displayed in the overlay. */
|
|
11
|
+
export interface FloatingReaction {
|
|
12
|
+
/** Stable unique id assigned by the adapter (used as React key / tracker). */
|
|
13
|
+
id: string
|
|
14
|
+
/** The emoji character(s) to display, e.g. '👋' or '❤️'. */
|
|
15
|
+
emoji: string
|
|
16
|
+
/**
|
|
17
|
+
* Horizontal offset bucket (0-indexed). Adapters use laneOffsetPercent() to
|
|
18
|
+
* convert this into a CSS left % so multiple reactions spread across the
|
|
19
|
+
* overlay rather than stacking.
|
|
20
|
+
*/
|
|
21
|
+
lane?: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* How long (ms) a floating reaction should live before the adapter removes it.
|
|
26
|
+
* Use this constant in adapters to drive setTimeout-based auto-expiry so the
|
|
27
|
+
* value stays in sync with the CSS animation duration.
|
|
28
|
+
*/
|
|
29
|
+
export const REACTION_LIFETIME_MS = 3000
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Compute the horizontal left-offset percentage for a given lane bucket.
|
|
33
|
+
*
|
|
34
|
+
* Pure and deterministic — safe to call in tests and SSR.
|
|
35
|
+
*
|
|
36
|
+
* @param lane Zero-indexed lane number (clamped to [0, lanes-1]).
|
|
37
|
+
* @param lanes Total number of lanes (default 5).
|
|
38
|
+
* @returns A percentage value in [0, 100].
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* laneOffsetPercent(0, 5) // 10
|
|
42
|
+
* laneOffsetPercent(2, 5) // 50
|
|
43
|
+
* laneOffsetPercent(4, 5) // 90
|
|
44
|
+
*/
|
|
45
|
+
export function laneOffsetPercent(lane = 0, lanes = 5): number {
|
|
46
|
+
if (lanes <= 0) return 50
|
|
47
|
+
const clamped = Math.max(0, Math.min(lane, lanes - 1))
|
|
48
|
+
// Divide [0,100] into `lanes` equal buckets and return the centre of each.
|
|
49
|
+
const step = 100 / lanes
|
|
50
|
+
return step * clamped + step / 2
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface FloatingReactionsAriaProps {
|
|
54
|
+
role: string
|
|
55
|
+
'aria-live': 'polite' | 'assertive' | 'off'
|
|
56
|
+
'aria-label': string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface FloatingReactionsAPI {
|
|
60
|
+
/** ARIA attributes to spread on the overlay container. */
|
|
61
|
+
ariaProps: FloatingReactionsAriaProps
|
|
62
|
+
/** Data attributes for styling hooks. */
|
|
63
|
+
dataAttributes: Record<string, string>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build the framework-agnostic accessibility and data props for the overlay
|
|
68
|
+
* container.
|
|
69
|
+
*
|
|
70
|
+
* The overlay is a `role="status"` live region so screen readers announce
|
|
71
|
+
* incoming reactions without interrupting the user (polite).
|
|
72
|
+
*/
|
|
73
|
+
export function createFloatingReactions(): FloatingReactionsAPI {
|
|
74
|
+
const ariaProps: FloatingReactionsAriaProps = {
|
|
75
|
+
role: 'status',
|
|
76
|
+
'aria-live': 'polite',
|
|
77
|
+
'aria-label': 'Reactions',
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const dataAttributes: Record<string, string> = {
|
|
81
|
+
'data-component': 'floating-reactions',
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { ariaProps, dataAttributes }
|
|
85
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createFloatingReactions,
|
|
3
|
+
laneOffsetPercent,
|
|
4
|
+
REACTION_LIFETIME_MS,
|
|
5
|
+
type FloatingReaction,
|
|
6
|
+
type FloatingReactionsAriaProps,
|
|
7
|
+
type FloatingReactionsAPI,
|
|
8
|
+
} from './floating-reactions.js'
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
floatingReactionsVariants,
|
|
12
|
+
floatingReactionItemClass,
|
|
13
|
+
floatingReactionLabelClass,
|
|
14
|
+
} from './floating-reactions.styles.js'
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Root wrapper for the flow editor canvas. Sized to fill its container and
|
|
5
|
+
* provides a relative positioning context for the node/edge layers.
|
|
6
|
+
*/
|
|
7
|
+
export const flowEditorVariants = cva({
|
|
8
|
+
base: 'relative w-full overflow-hidden bg-background border border-border rounded-lg',
|
|
9
|
+
variants: {
|
|
10
|
+
size: {
|
|
11
|
+
sm: 'h-64',
|
|
12
|
+
md: 'h-96',
|
|
13
|
+
lg: 'h-[32rem]',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
size: 'md',
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A single node box. The `selected` variant adds a ring to signal the active
|
|
23
|
+
* selection; semantic token classes are used throughout so theming works
|
|
24
|
+
* without overrides.
|
|
25
|
+
*/
|
|
26
|
+
export const flowNodeVariants = cva({
|
|
27
|
+
base: [
|
|
28
|
+
'absolute flex items-center justify-center rounded-md border',
|
|
29
|
+
'bg-card border-border text-card-foreground text-sm font-medium',
|
|
30
|
+
'cursor-pointer select-none transition-shadow',
|
|
31
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
|
|
32
|
+
].join(' '),
|
|
33
|
+
variants: {
|
|
34
|
+
selected: {
|
|
35
|
+
true: 'ring-2 ring-ring ring-primary shadow-md',
|
|
36
|
+
false: 'hover:border-primary/50 hover:shadow-sm',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
defaultVariants: {
|
|
40
|
+
selected: 'false',
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
/** Accessible label text inside a node box. */
|
|
45
|
+
export const flowNodeLabelClass = 'px-3 py-2 truncate pointer-events-none'
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* SVG `<path>` element class for an edge. `currentColor` is used so the
|
|
49
|
+
* stroke inherits from `text-muted-foreground` set on the SVG container.
|
|
50
|
+
*/
|
|
51
|
+
export const flowEdgePathClass =
|
|
52
|
+
'fill-none stroke-current stroke-2 transition-opacity'
|
|
53
|
+
|
|
54
|
+
/** Text label positioned at the midpoint of an edge. */
|
|
55
|
+
export const flowEdgeLabelClass =
|
|
56
|
+
'text-xs fill-muted-foreground select-none pointer-events-none'
|