@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.
Files changed (162) hide show
  1. package/dist/astro-audience-feature-card/AudienceFeatureCard.astro +53 -0
  2. package/dist/astro-audience-feature-card/index.ts +1 -0
  3. package/dist/astro-audio-room/AudioRoom.astro +81 -0
  4. package/dist/astro-audio-room/index.ts +1 -0
  5. package/dist/astro-brand-network-cell/BrandNetworkCell.astro +77 -0
  6. package/dist/astro-brand-network-cell/index.ts +1 -0
  7. package/dist/astro-browser-chrome-mock/BrowserChromeMock.astro +62 -0
  8. package/dist/astro-browser-chrome-mock/index.ts +1 -0
  9. package/dist/astro-call-controls/CallControls.astro +57 -0
  10. package/dist/astro-call-controls/index.ts +1 -0
  11. package/dist/astro-checklist/Checklist.astro +76 -0
  12. package/dist/astro-checklist/index.ts +1 -0
  13. package/dist/astro-editor-status-bar/EditorStatusBar.astro +92 -0
  14. package/dist/astro-editor-status-bar/index.ts +1 -0
  15. package/dist/astro-editor-tabs/EditorTabs.astro +74 -0
  16. package/dist/astro-editor-tabs/index.ts +1 -0
  17. package/dist/astro-floating-reactions/FloatingReactions.astro +56 -0
  18. package/dist/astro-floating-reactions/index.ts +1 -0
  19. package/dist/astro-flow-editor/FlowEditor.astro +99 -0
  20. package/dist/astro-flow-editor/index.ts +3 -0
  21. package/dist/astro-graph-view/GraphView.astro +109 -0
  22. package/dist/astro-graph-view/index.ts +1 -0
  23. package/dist/astro-infinite-canvas/InfiniteCanvas.astro +43 -0
  24. package/dist/astro-infinite-canvas/index.ts +1 -0
  25. package/dist/astro-live-captions/LiveCaptions.astro +54 -0
  26. package/dist/astro-live-captions/index.ts +1 -0
  27. package/dist/astro-live-cursors/LiveCursors.astro +55 -0
  28. package/dist/astro-live-cursors/index.ts +1 -0
  29. package/dist/astro-live-transcript/LiveTranscript.astro +55 -0
  30. package/dist/astro-live-transcript/index.ts +1 -0
  31. package/dist/astro-marquee-strip/MarqueeStrip.astro +63 -0
  32. package/dist/astro-marquee-strip/index.ts +1 -0
  33. package/dist/astro-mastery-bar/MasteryBar.astro +56 -0
  34. package/dist/astro-mastery-bar/index.ts +1 -0
  35. package/dist/astro-mini-map/MiniMap.astro +74 -0
  36. package/dist/astro-mini-map/index.ts +1 -0
  37. package/dist/astro-numbered-steps/NumberedSteps.astro +47 -0
  38. package/dist/astro-numbered-steps/index.ts +1 -0
  39. package/dist/astro-pre-call-lobby/PreCallLobby.astro +202 -0
  40. package/dist/astro-pre-call-lobby/index.ts +1 -0
  41. package/dist/astro-pricing-card/PricingCard.astro +114 -0
  42. package/dist/astro-pricing-card/index.ts +1 -0
  43. package/dist/astro-radial-gauge/RadialGauge.astro +144 -0
  44. package/dist/astro-radial-gauge/index.ts +1 -0
  45. package/dist/astro-rating-scale/RatingScale.astro +73 -0
  46. package/dist/astro-rating-scale/index.ts +1 -0
  47. package/dist/astro-section-head/SectionHead.astro +44 -0
  48. package/dist/astro-section-head/index.ts +1 -0
  49. package/dist/astro-stat-grid/StatGrid.astro +46 -0
  50. package/dist/astro-stat-grid/index.ts +1 -0
  51. package/dist/astro-sticky-note/StickyNote.astro +55 -0
  52. package/dist/astro-sticky-note/index.ts +1 -0
  53. package/dist/astro-terminal/Terminal.astro +67 -0
  54. package/dist/astro-terminal/index.ts +1 -0
  55. package/dist/astro-test-results/TestResults.astro +76 -0
  56. package/dist/astro-test-results/index.ts +1 -0
  57. package/dist/astro-timeline/Timeline.astro +59 -0
  58. package/dist/astro-timeline/index.ts +1 -0
  59. package/dist/astro-video-grid/VideoGrid.astro +182 -0
  60. package/dist/astro-video-grid/index.ts +1 -0
  61. package/dist/astro-video-tile/VideoTile.astro +104 -0
  62. package/dist/astro-video-tile/index.ts +1 -0
  63. package/dist/astro-wizard/Wizard.astro +137 -0
  64. package/dist/astro-wizard/index.ts +1 -0
  65. package/dist/audience-feature-card/audience-feature-card.styles.ts +23 -0
  66. package/dist/audience-feature-card/audience-feature-card.ts +39 -0
  67. package/dist/audience-feature-card/index.ts +13 -0
  68. package/dist/audio-room/audio-room.styles.ts +88 -0
  69. package/dist/audio-room/audio-room.ts +70 -0
  70. package/dist/audio-room/index.ts +16 -0
  71. package/dist/brand-network-cell/brand-network-cell.styles.ts +46 -0
  72. package/dist/brand-network-cell/brand-network-cell.ts +35 -0
  73. package/dist/brand-network-cell/index.ts +14 -0
  74. package/dist/browser-chrome-mock/browser-chrome-mock.styles.ts +43 -0
  75. package/dist/browser-chrome-mock/browser-chrome-mock.ts +52 -0
  76. package/dist/browser-chrome-mock/index.ts +17 -0
  77. package/dist/call-controls/call-controls.styles.ts +63 -0
  78. package/dist/call-controls/call-controls.ts +79 -0
  79. package/dist/call-controls/index.ts +15 -0
  80. package/dist/checklist/checklist.styles.ts +76 -0
  81. package/dist/checklist/checklist.ts +62 -0
  82. package/dist/checklist/index.ts +18 -0
  83. package/dist/editor-status-bar/editor-status-bar.styles.ts +40 -0
  84. package/dist/editor-status-bar/editor-status-bar.ts +78 -0
  85. package/dist/editor-status-bar/index.ts +15 -0
  86. package/dist/editor-tabs/editor-tabs.styles.ts +52 -0
  87. package/dist/editor-tabs/editor-tabs.ts +82 -0
  88. package/dist/editor-tabs/index.ts +15 -0
  89. package/dist/floating-reactions/floating-reactions.styles.ts +54 -0
  90. package/dist/floating-reactions/floating-reactions.ts +85 -0
  91. package/dist/floating-reactions/index.ts +14 -0
  92. package/dist/flow-editor/flow-editor.styles.ts +56 -0
  93. package/dist/flow-editor/flow-editor.ts +126 -0
  94. package/dist/flow-editor/index.ts +18 -0
  95. package/dist/graph-view/graph-view.styles.ts +66 -0
  96. package/dist/graph-view/graph-view.ts +130 -0
  97. package/dist/graph-view/index.ts +21 -0
  98. package/dist/index.ts +32 -0
  99. package/dist/infinite-canvas/index.ts +17 -0
  100. package/dist/infinite-canvas/infinite-canvas.styles.ts +49 -0
  101. package/dist/infinite-canvas/infinite-canvas.ts +97 -0
  102. package/dist/live-captions/index.ts +13 -0
  103. package/dist/live-captions/live-captions.styles.ts +45 -0
  104. package/dist/live-captions/live-captions.ts +57 -0
  105. package/dist/live-cursors/index.ts +14 -0
  106. package/dist/live-cursors/live-cursors.styles.ts +41 -0
  107. package/dist/live-cursors/live-cursors.ts +73 -0
  108. package/dist/live-transcript/index.ts +16 -0
  109. package/dist/live-transcript/live-transcript.styles.ts +48 -0
  110. package/dist/live-transcript/live-transcript.ts +82 -0
  111. package/dist/marquee-strip/index.ts +14 -0
  112. package/dist/marquee-strip/marquee-strip.styles.ts +61 -0
  113. package/dist/marquee-strip/marquee-strip.ts +35 -0
  114. package/dist/mastery-bar/index.ts +15 -0
  115. package/dist/mastery-bar/mastery-bar.styles.ts +45 -0
  116. package/dist/mastery-bar/mastery-bar.ts +55 -0
  117. package/dist/mini-map/index.ts +16 -0
  118. package/dist/mini-map/mini-map.styles.ts +32 -0
  119. package/dist/mini-map/mini-map.ts +122 -0
  120. package/dist/numbered-steps/index.ts +16 -0
  121. package/dist/numbered-steps/numbered-steps.styles.ts +36 -0
  122. package/dist/numbered-steps/numbered-steps.ts +64 -0
  123. package/dist/pre-call-lobby/index.ts +22 -0
  124. package/dist/pre-call-lobby/pre-call-lobby.styles.ts +108 -0
  125. package/dist/pre-call-lobby/pre-call-lobby.ts +61 -0
  126. package/dist/pricing-card/index.ts +19 -0
  127. package/dist/pricing-card/pricing-card.styles.ts +71 -0
  128. package/dist/pricing-card/pricing-card.ts +37 -0
  129. package/dist/radial-gauge/index.ts +26 -0
  130. package/dist/radial-gauge/radial-gauge.styles.ts +75 -0
  131. package/dist/radial-gauge/radial-gauge.ts +183 -0
  132. package/dist/rating-scale/index.ts +16 -0
  133. package/dist/rating-scale/rating-scale.styles.ts +48 -0
  134. package/dist/rating-scale/rating-scale.ts +101 -0
  135. package/dist/section-head/index.ts +13 -0
  136. package/dist/section-head/section-head.styles.ts +30 -0
  137. package/dist/section-head/section-head.ts +28 -0
  138. package/dist/stat-grid/index.ts +13 -0
  139. package/dist/stat-grid/stat-grid.styles.ts +25 -0
  140. package/dist/stat-grid/stat-grid.ts +41 -0
  141. package/dist/sticky-note/index.ts +14 -0
  142. package/dist/sticky-note/sticky-note.styles.ts +37 -0
  143. package/dist/sticky-note/sticky-note.ts +47 -0
  144. package/dist/terminal/index.ts +13 -0
  145. package/dist/terminal/terminal.styles.ts +56 -0
  146. package/dist/terminal/terminal.ts +60 -0
  147. package/dist/test-results/index.ts +19 -0
  148. package/dist/test-results/test-results.styles.ts +93 -0
  149. package/dist/test-results/test-results.ts +83 -0
  150. package/dist/timeline/index.ts +22 -0
  151. package/dist/timeline/timeline.styles.ts +98 -0
  152. package/dist/timeline/timeline.ts +86 -0
  153. package/dist/video-grid/index.ts +16 -0
  154. package/dist/video-grid/video-grid.styles.ts +38 -0
  155. package/dist/video-grid/video-grid.ts +93 -0
  156. package/dist/video-tile/index.ts +17 -0
  157. package/dist/video-tile/video-tile.styles.ts +59 -0
  158. package/dist/video-tile/video-tile.ts +74 -0
  159. package/dist/wizard/index.ts +28 -0
  160. package/dist/wizard/wizard.styles.ts +147 -0
  161. package/dist/wizard/wizard.ts +128 -0
  162. package/package.json +33 -1
@@ -0,0 +1,183 @@
1
+
2
+ // ---------------------------------------------------------------------------
3
+ // Types
4
+ // ---------------------------------------------------------------------------
5
+
6
+ /** The visual size of the gauge. */
7
+ export type GaugeSize = 'sm' | 'md' | 'lg'
8
+
9
+ /** Tone mapped to a zone range. */
10
+ export type GaugeTone = 'default' | 'success' | 'warning' | 'danger'
11
+
12
+ /**
13
+ * A threshold zone that applies a semantic tone when the gauge value falls
14
+ * at or below `upTo`. Zones should be listed in ascending `upTo` order; the
15
+ * first zone whose `upTo >= value` wins.
16
+ */
17
+ export interface GaugeZone {
18
+ /** Value up to (and including) which this zone applies. */
19
+ upTo: number
20
+ /** Semantic tone for the arc while the value is in this zone. */
21
+ tone: GaugeTone
22
+ }
23
+
24
+ export interface RadialGaugeProps {
25
+ /** Current gauge value. */
26
+ value: number
27
+ /** Minimum possible value. */
28
+ min?: number
29
+ /** Maximum possible value. */
30
+ max?: number
31
+ /** Visual size. */
32
+ size?: GaugeSize
33
+ }
34
+
35
+ export interface RadialGaugeAPI {
36
+ /** ARIA attributes to spread on the SVG element (`role="meter"`). */
37
+ ariaProps: Record<string, string | number>
38
+ /** Data attributes for styling hooks. */
39
+ dataAttributes: Record<string, string>
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Pixel diameters for each size variant (exported for use in adapters)
44
+ // ---------------------------------------------------------------------------
45
+ export const GAUGE_SIZE_PX: Record<GaugeSize, number> = {
46
+ sm: 80,
47
+ md: 120,
48
+ lg: 160,
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Pure geometry helpers
53
+ // ---------------------------------------------------------------------------
54
+
55
+ /**
56
+ * Clamp `value` to the inclusive range `[min, max]`.
57
+ */
58
+ export function clampValue(value: number, min: number, max: number): number {
59
+ return Math.min(Math.max(value, min), max)
60
+ }
61
+
62
+ /**
63
+ * Map a `value` within `[min, max]` to a fraction in `[0, 1]`.
64
+ * Returns 0 when `min === max` to avoid division by zero.
65
+ */
66
+ export function valueToFraction(value: number, min: number, max: number): number {
67
+ if (max === min) return 0
68
+ return clampValue((value - min) / (max - min), 0, 1)
69
+ }
70
+
71
+ /**
72
+ * Convert polar coordinates to Cartesian.
73
+ * `angleDeg` is measured clockwise from the positive x-axis (SVG convention).
74
+ */
75
+ export function polarToCartesian(
76
+ cx: number,
77
+ cy: number,
78
+ r: number,
79
+ angleDeg: number,
80
+ ): { x: number; y: number } {
81
+ const rad = ((angleDeg - 90) * Math.PI) / 180
82
+ return {
83
+ x: cx + r * Math.cos(rad),
84
+ y: cy + r * Math.sin(rad),
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Build an SVG arc path `d` string from `startAngleDeg` to `endAngleDeg`
90
+ * (both measured clockwise from the top / 12-o'clock position).
91
+ *
92
+ * Returns an empty string when the arc span is zero.
93
+ */
94
+ export function describeArc(
95
+ cx: number,
96
+ cy: number,
97
+ r: number,
98
+ startAngleDeg: number,
99
+ endAngleDeg: number,
100
+ ): string {
101
+ const span = endAngleDeg - startAngleDeg
102
+ if (span === 0) return ''
103
+
104
+ // For a full circle we need two arcs to avoid SVG treating start === end as
105
+ // a no-op. Shift the end by 0.001° so the path stays valid.
106
+ const clampedEnd = span >= 360 ? startAngleDeg + 359.999 : endAngleDeg
107
+
108
+ const start = polarToCartesian(cx, cy, r, startAngleDeg)
109
+ const end = polarToCartesian(cx, cy, r, clampedEnd)
110
+ const largeArc = clampedEnd - startAngleDeg > 180 ? 1 : 0
111
+
112
+ return [
113
+ `M ${start.x} ${start.y}`,
114
+ `A ${r} ${r} 0 ${largeArc} 1 ${end.x} ${end.y}`,
115
+ ].join(' ')
116
+ }
117
+
118
+ /**
119
+ * Compute the `stroke-dashoffset` for a full-circle ring gauge.
120
+ *
121
+ * With `stroke-dasharray="circumference circumference"` and this offset, the
122
+ * visible portion of the stroke equals `fraction × circumference`.
123
+ */
124
+ export function gaugeStrokeDashoffset(fraction: number, circumference: number): number {
125
+ return circumference * (1 - fraction)
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Zone resolution
130
+ // ---------------------------------------------------------------------------
131
+
132
+ /**
133
+ * Find the active zone tone for the current `value`.
134
+ * Zones are tested in ascending `upTo` order; the first zone whose
135
+ * `upTo >= value` wins. Falls back to `'default'` when no zone matches.
136
+ */
137
+ export function resolveZoneTone(
138
+ value: number,
139
+ zones: GaugeZone[],
140
+ _max: number,
141
+ ): GaugeTone {
142
+ if (!zones || zones.length === 0) return 'default'
143
+
144
+ // Sort a copy ascending by upTo so callers don't have to pre-sort.
145
+ const sorted = [...zones].sort((a, b) => a.upTo - b.upTo)
146
+
147
+ for (const zone of sorted) {
148
+ if (value <= zone.upTo) return zone.tone
149
+ }
150
+
151
+ // Value is above all zone thresholds — use the last zone's tone.
152
+ return sorted[sorted.length - 1].tone
153
+ }
154
+
155
+ // ---------------------------------------------------------------------------
156
+ // Factory
157
+ // ---------------------------------------------------------------------------
158
+
159
+ /**
160
+ * Build the framework-agnostic props for a radial gauge element.
161
+ *
162
+ * Returns `role="meter"` plus ARIA value attributes and data attributes;
163
+ * adapters spread these onto their `<svg>` element.
164
+ */
165
+ export function createRadialGauge(props: RadialGaugeProps): RadialGaugeAPI {
166
+ const { value, min = 0, max = 100, size = 'md' } = props
167
+
168
+ const clamped = clampValue(value, min, max)
169
+
170
+ const ariaProps: Record<string, string | number> = {
171
+ role: 'meter',
172
+ 'aria-valuenow': clamped,
173
+ 'aria-valuemin': min,
174
+ 'aria-valuemax': max,
175
+ }
176
+
177
+ const dataAttributes: Record<string, string> = {
178
+ 'data-size': size,
179
+ 'data-value': String(clamped),
180
+ }
181
+
182
+ return { ariaProps, dataAttributes }
183
+ }
@@ -0,0 +1,16 @@
1
+ export {
2
+ createRatingScale,
3
+ resolveRatingPoints,
4
+ getNextRatingIndex,
5
+ type RatingScaleSize,
6
+ type RatingScalePoint,
7
+ type RatingScaleProps,
8
+ type RatingScaleAPI,
9
+ } from './rating-scale.js'
10
+
11
+ export {
12
+ ratingScaleVariants,
13
+ ratingScaleTrackClass,
14
+ ratingScaleItemVariants,
15
+ ratingScaleLabelClass,
16
+ } from './rating-scale.styles.js'
@@ -0,0 +1,48 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /** Container row that holds the points plus optional end labels. */
4
+ export const ratingScaleVariants = cva({
5
+ base: 'inline-flex items-center gap-3',
6
+ variants: {
7
+ size: {
8
+ sm: '',
9
+ md: '',
10
+ },
11
+ },
12
+ defaultVariants: {
13
+ size: 'md',
14
+ },
15
+ })
16
+
17
+ /** Row of point buttons (the scale itself, between the end labels). */
18
+ export const ratingScaleTrackClass = 'inline-flex items-center gap-1.5'
19
+
20
+ /**
21
+ * A single point button. The `state` variant carries the selected vs unselected
22
+ * treatment so adapters never inline these classes.
23
+ */
24
+ export const ratingScaleItemVariants = cva({
25
+ base: [
26
+ 'inline-flex items-center justify-center rounded-md border font-medium tabular-nums',
27
+ 'transition-colors focus-visible:outline-none focus-visible:ring-2',
28
+ 'focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
29
+ 'disabled:pointer-events-none disabled:opacity-50',
30
+ ].join(' '),
31
+ variants: {
32
+ size: {
33
+ sm: 'size-8 text-xs',
34
+ md: 'size-10 text-sm',
35
+ },
36
+ state: {
37
+ checked: 'border-primary bg-primary text-primary-foreground',
38
+ unchecked: 'border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground',
39
+ },
40
+ },
41
+ defaultVariants: {
42
+ size: 'md',
43
+ state: 'unchecked',
44
+ },
45
+ })
46
+
47
+ /** End-label text flanking the scale (e.g. "Never seen this" / "Could write it"). */
48
+ export const ratingScaleLabelClass = 'text-xs text-muted-foreground'
@@ -0,0 +1,101 @@
1
+ import type { AccessibilityProps } from '../shared/index.ts'
2
+
3
+ /** Visual size of a rating scale. */
4
+ export type RatingScaleSize = 'sm' | 'md'
5
+
6
+ /** A single selectable point on the scale (1-indexed value + accessible label). */
7
+ export interface RatingScalePoint {
8
+ /** The value this point represents. */
9
+ value: number
10
+ /** Accessible label for the point (falls back to the value). */
11
+ label?: string
12
+ }
13
+
14
+ export interface RatingScaleProps {
15
+ /** Currently selected value (controlled). */
16
+ value?: number
17
+ /** Number of points on the scale. Ignored when {@link points} is given. */
18
+ count?: number
19
+ /** Visual size. */
20
+ size?: RatingScaleSize
21
+ }
22
+
23
+ export interface RatingScaleAPI {
24
+ /** ARIA attributes to spread on the group element (`role="radiogroup"`). */
25
+ ariaProps: Partial<AccessibilityProps>
26
+ /** Data attributes for styling hooks. */
27
+ dataAttributes: Record<string, string>
28
+ }
29
+
30
+ /**
31
+ * Build the framework-agnostic group props for a rating / Likert scale.
32
+ *
33
+ * Returns `role="radiogroup"` plus data attributes; adapters spread these onto
34
+ * their container and render the points themselves. A rating scale is a
35
+ * single-select control with radio semantics, like a segmented control, but
36
+ * tuned for ordinal self-rating (e.g. a 1–5 Likert prompt with end labels).
37
+ */
38
+ export function createRatingScale(props: RatingScaleProps = {}): RatingScaleAPI {
39
+ const { value, size = 'md' } = props
40
+
41
+ const ariaProps: Partial<AccessibilityProps> = {
42
+ role: 'radiogroup',
43
+ }
44
+
45
+ const dataAttributes: Record<string, string> = {
46
+ 'data-size': size,
47
+ }
48
+ if (value !== undefined) {
49
+ dataAttributes['data-value'] = String(value)
50
+ }
51
+
52
+ return { ariaProps, dataAttributes }
53
+ }
54
+
55
+ /**
56
+ * Resolve the list of points for a scale from either an explicit list or a
57
+ * `count` (producing `1..count`). The single source of truth shared by every
58
+ * adapter so a `count={5}` scale renders identical points everywhere.
59
+ */
60
+ export function resolveRatingPoints(
61
+ points: RatingScalePoint[] | undefined,
62
+ count = 5,
63
+ ): RatingScalePoint[] {
64
+ if (points && points.length > 0) return points
65
+ return Array.from({ length: Math.max(0, count) }, (_, i) => ({ value: i + 1 }))
66
+ }
67
+
68
+ /**
69
+ * Pure keyboard-navigation rule shared by every adapter.
70
+ *
71
+ * Unlike a wrapping segmented control, an ordinal rating scale **clamps** at
72
+ * the ends (you can't wrap from "5 — strongly agree" back to "1"). Home/End
73
+ * jump to the extremes; unhandled keys return `current` unchanged.
74
+ *
75
+ * - ArrowRight / ArrowUp → next (clamped to last)
76
+ * - ArrowLeft / ArrowDown → previous (clamped to first)
77
+ * - Home → first
78
+ * - End → last
79
+ */
80
+ export function getNextRatingIndex(
81
+ current: number,
82
+ key: string,
83
+ count: number,
84
+ ): number {
85
+ if (count <= 0) return current
86
+
87
+ switch (key) {
88
+ case 'ArrowRight':
89
+ case 'ArrowUp':
90
+ return Math.min(current + 1, count - 1)
91
+ case 'ArrowLeft':
92
+ case 'ArrowDown':
93
+ return Math.max(current - 1, 0)
94
+ case 'Home':
95
+ return 0
96
+ case 'End':
97
+ return count - 1
98
+ default:
99
+ return current
100
+ }
101
+ }
@@ -0,0 +1,13 @@
1
+ export {
2
+ createSectionHead,
3
+ type SectionHeadAlign,
4
+ type SectionHeadProps,
5
+ type SectionHeadAPI,
6
+ } from './section-head.js'
7
+
8
+ export {
9
+ sectionHeadVariants,
10
+ sectionHeadKickerClass,
11
+ sectionHeadTitleClass,
12
+ sectionHeadLedeClass,
13
+ } from './section-head.styles.js'
@@ -0,0 +1,30 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * Container variant that controls the alignment of the whole cluster.
5
+ * Center: auto-margins + text-center + constrained max-width for comfortable
6
+ * line length. Left: no margin constraint, flush left.
7
+ */
8
+ export const sectionHeadVariants = cva({
9
+ base: 'flex flex-col',
10
+ variants: {
11
+ align: {
12
+ center: 'mx-auto text-center max-w-2xl',
13
+ left: 'text-left',
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ align: 'center',
18
+ },
19
+ })
20
+
21
+ /** Small-caps eyebrow label above the title. */
22
+ export const sectionHeadKickerClass =
23
+ 'text-[11px] uppercase tracking-widest text-primary font-semibold'
24
+
25
+ /** Primary h2 heading. */
26
+ export const sectionHeadTitleClass =
27
+ 'text-3xl sm:text-4xl font-bold tracking-tight text-foreground'
28
+
29
+ /** Optional lede paragraph below the title. */
30
+ export const sectionHeadLedeClass = 'mt-3 text-muted-foreground leading-relaxed'
@@ -0,0 +1,28 @@
1
+ /** Horizontal alignment of the section head cluster. */
2
+ export type SectionHeadAlign = 'center' | 'left'
3
+
4
+ export interface SectionHeadProps {
5
+ /** Alignment of the kicker, title, and lede. */
6
+ align?: SectionHeadAlign
7
+ }
8
+
9
+ export interface SectionHeadAPI {
10
+ /** Data attributes for styling hooks. */
11
+ dataAttributes: Record<string, string>
12
+ }
13
+
14
+ /**
15
+ * Build the framework-agnostic data attributes for a SectionHead.
16
+ *
17
+ * Returns data-align so adapters can apply styles via CSS or Tailwind
18
+ * variants. All visual logic (classes, cva) lives in `section-head.styles.ts`.
19
+ */
20
+ export function createSectionHead(props: SectionHeadProps = {}): SectionHeadAPI {
21
+ const { align = 'center' } = props
22
+
23
+ const dataAttributes: Record<string, string> = {
24
+ 'data-align': align,
25
+ }
26
+
27
+ return { dataAttributes }
28
+ }
@@ -0,0 +1,13 @@
1
+ export {
2
+ createStatGrid,
3
+ statColumns,
4
+ type StatItem,
5
+ type StatGridAPI,
6
+ } from './stat-grid.js'
7
+
8
+ export {
9
+ statGridVariants,
10
+ statItemClass,
11
+ statValueClass,
12
+ statLabelClass,
13
+ } from './stat-grid.styles.js'
@@ -0,0 +1,25 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /** Grid container that lays out stat items. */
4
+ export const statGridVariants = cva({
5
+ base: 'grid gap-8',
6
+ variants: {
7
+ columns: {
8
+ '1': 'grid-cols-1',
9
+ '2': 'grid-cols-2',
10
+ '3': 'grid-cols-3',
11
+ },
12
+ },
13
+ defaultVariants: {
14
+ columns: '3',
15
+ },
16
+ })
17
+
18
+ /** Individual stat item container (role="listitem"). */
19
+ export const statItemClass = 'flex flex-col'
20
+
21
+ /** The large bold value of the stat. */
22
+ export const statValueClass = 'text-xl sm:text-2xl font-bold text-primary'
23
+
24
+ /** The descriptive label below the value. */
25
+ export const statLabelClass = 'mt-1 text-sm text-muted-foreground leading-snug'
@@ -0,0 +1,41 @@
1
+ /** A single stat callout displayed in the grid. */
2
+ export type StatItem = {
3
+ /** The primary value displayed prominently (e.g. "10k+", "$4.2M"). */
4
+ value: string
5
+ /** The descriptive label shown below the value. */
6
+ label: string
7
+ }
8
+
9
+ export interface StatGridAPI {
10
+ /** ARIA attributes to spread on the grid container element (`role="list"`). */
11
+ ariaProps: { role: 'list' }
12
+ /** Data attributes for styling hooks. */
13
+ dataAttributes: Record<string, string>
14
+ }
15
+
16
+ /**
17
+ * Compute the number of grid columns for a stat grid.
18
+ *
19
+ * - 1 item → 1 column
20
+ * - 2 items → 2 columns
21
+ * - 3 or more items → capped at `max` (default 3)
22
+ */
23
+ export function statColumns(count: number, max = 3): number {
24
+ if (count <= 0) return 1
25
+ if (count === 1) return 1
26
+ if (count === 2) return 2
27
+ return Math.min(count, max)
28
+ }
29
+
30
+ /**
31
+ * Build the framework-agnostic container props for a stat grid.
32
+ *
33
+ * Returns `role="list"` and data attributes; adapters spread these onto their
34
+ * container element and render the items themselves.
35
+ */
36
+ export function createStatGrid(): StatGridAPI {
37
+ return {
38
+ ariaProps: { role: 'list' },
39
+ dataAttributes: {},
40
+ }
41
+ }
@@ -0,0 +1,14 @@
1
+ export {
2
+ STICKY_NOTE_COLORS,
3
+ nextStickyColor,
4
+ createStickyNote,
5
+ type StickyNoteColor,
6
+ type CreateStickyNoteOptions,
7
+ type StickyNoteAPI,
8
+ } from './sticky-note.js'
9
+
10
+ export {
11
+ stickyNoteVariants,
12
+ stickyNoteTextClass,
13
+ stickyNoteAuthorClass,
14
+ } from './sticky-note.styles.js'
@@ -0,0 +1,37 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * Note container with color variant.
5
+ *
6
+ * Sticky notes are intrinsically colorful — an approved exception to the
7
+ * semantic-token-only rule. Each color maps to a soft tint background with a
8
+ * readable dark text.
9
+ */
10
+ export const stickyNoteVariants = cva({
11
+ base: [
12
+ 'relative inline-flex flex-col gap-2 rounded-md p-4',
13
+ 'shadow-md',
14
+ 'min-w-[160px] min-h-[120px]',
15
+ ].join(' '),
16
+ variants: {
17
+ color: {
18
+ yellow: 'bg-yellow-100 text-yellow-900',
19
+ pink: 'bg-pink-100 text-pink-900',
20
+ blue: 'bg-blue-100 text-blue-900',
21
+ green: 'bg-green-100 text-green-900',
22
+ purple: 'bg-purple-100 text-purple-900',
23
+ orange: 'bg-orange-100 text-orange-900',
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ color: 'yellow',
28
+ },
29
+ })
30
+
31
+ /** Class for the editable/static text area inside the note. */
32
+ export const stickyNoteTextClass =
33
+ 'flex-1 w-full resize-none bg-transparent text-sm leading-relaxed placeholder:opacity-60 outline-none'
34
+
35
+ /** Class for the author chip rendered in the note footer. */
36
+ export const stickyNoteAuthorClass =
37
+ 'mt-auto text-xs font-medium opacity-70 truncate'
@@ -0,0 +1,47 @@
1
+ /** The available color variants for a sticky note. */
2
+ export type StickyNoteColor = 'yellow' | 'pink' | 'blue' | 'green' | 'purple' | 'orange'
3
+
4
+ /** Ordered list of all sticky note colors — used for cycling and iteration. */
5
+ export const STICKY_NOTE_COLORS: StickyNoteColor[] = [
6
+ 'yellow',
7
+ 'pink',
8
+ 'blue',
9
+ 'green',
10
+ 'purple',
11
+ 'orange',
12
+ ]
13
+
14
+ /**
15
+ * Returns the next color in the cycle. Wraps from the last color back to the
16
+ * first. Useful for "add note" flows that auto-assign colors in sequence.
17
+ */
18
+ export function nextStickyColor(color: StickyNoteColor): StickyNoteColor {
19
+ const index = STICKY_NOTE_COLORS.indexOf(color)
20
+ const next = (index + 1) % STICKY_NOTE_COLORS.length
21
+ return STICKY_NOTE_COLORS[next]
22
+ }
23
+
24
+ export interface CreateStickyNoteOptions {
25
+ /** Color of the note. */
26
+ color: StickyNoteColor
27
+ }
28
+
29
+ export interface StickyNoteAPI {
30
+ /** Data attributes for styling hooks; spread onto the note container. */
31
+ dataAttributes: { 'data-color': StickyNoteColor }
32
+ /** ARIA props for the note container. */
33
+ ariaProps: { role: 'group' }
34
+ }
35
+
36
+ /**
37
+ * Build the framework-agnostic props for a sticky note container.
38
+ *
39
+ * Returns `role="group"` plus the `data-color` attribute that adapters use
40
+ * as a styling hook. Logic is pure and SSR-safe — no Math.random, no Date.
41
+ */
42
+ export function createStickyNote(options: CreateStickyNoteOptions): StickyNoteAPI {
43
+ return {
44
+ dataAttributes: { 'data-color': options.color },
45
+ ariaProps: { role: 'group' },
46
+ }
47
+ }
@@ -0,0 +1,13 @@
1
+ export {
2
+ formatPrompt,
3
+ createTerminal,
4
+ type TerminalLineKind,
5
+ type TerminalLine,
6
+ type TerminalAPI,
7
+ } from './terminal.js'
8
+
9
+ export {
10
+ terminalVariants,
11
+ terminalLineVariants,
12
+ terminalPromptClass,
13
+ } from './terminal.styles.js'
@@ -0,0 +1,56 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * The outer scroll container — a dark monospaced surface that houses all
5
+ * terminal lines. Uses semantic card tokens so it inherits the active theme.
6
+ */
7
+ export const terminalVariants = cva({
8
+ base: [
9
+ 'relative rounded-lg border border-border bg-card font-mono text-sm',
10
+ 'overflow-y-auto overflow-x-auto',
11
+ 'p-4 space-y-0.5',
12
+ ].join(' '),
13
+ variants: {
14
+ maxHeight: {
15
+ sm: 'max-h-48',
16
+ md: 'max-h-80',
17
+ lg: 'max-h-[32rem]',
18
+ none: '',
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ maxHeight: 'md',
23
+ },
24
+ })
25
+
26
+ /**
27
+ * A single terminal line. The `kind` variant maps each semantic category to
28
+ * the appropriate semantic token class — no raw color values inline.
29
+ */
30
+ export const terminalLineVariants = cva({
31
+ base: 'block whitespace-pre-wrap break-all leading-relaxed',
32
+ variants: {
33
+ kind: {
34
+ /** The user-typed command: full foreground, bold weight. */
35
+ command: 'text-foreground font-semibold',
36
+ /** Normal program output. */
37
+ stdout: 'text-foreground',
38
+ /** Error / stderr output — uses the destructive semantic token. */
39
+ stderr: 'text-destructive',
40
+ /** Informational summary lines — muted, visually de-emphasised. */
41
+ info: 'text-muted-foreground',
42
+ /**
43
+ * Success / passing lines — green-family.
44
+ * Maps to `text-emerald-500` via the Tailwind semantic alias used
45
+ * across the design system; downstream themes can override this token.
46
+ */
47
+ success: 'text-emerald-500 dark:text-emerald-400',
48
+ },
49
+ },
50
+ defaultVariants: {
51
+ kind: 'stdout',
52
+ },
53
+ })
54
+
55
+ /** The prompt symbol (`$`, `❯`, …) preceding a command line. */
56
+ export const terminalPromptClass = 'select-none mr-2 text-muted-foreground'