@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,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,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,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'
|