@a4ui/core 0.31.1 → 0.33.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 (60) hide show
  1. package/dist/NumberInput-Bnh2u7pp.js +381 -0
  2. package/dist/charts/BarList.d.ts +33 -0
  3. package/dist/charts/CategoryBar.d.ts +23 -0
  4. package/dist/charts/GaugeChart.d.ts +43 -0
  5. package/dist/charts/LineChart.d.ts +39 -0
  6. package/dist/charts/RadarChart.d.ts +34 -0
  7. package/dist/charts/StatusTracker.d.ts +27 -0
  8. package/dist/charts/index.d.ts +6 -0
  9. package/dist/charts.js +693 -100
  10. package/dist/commerce.js +2 -2
  11. package/dist/elements.css +206 -0
  12. package/dist/full.css +206 -0
  13. package/dist/index.d.ts +18 -1
  14. package/dist/index.js +5086 -3915
  15. package/dist/motion-jYMWmZqB.js +119 -0
  16. package/dist/ui/AnimatedBeam.d.ts +34 -0
  17. package/dist/ui/BentoGrid.d.ts +39 -0
  18. package/dist/ui/BorderBeam.d.ts +27 -0
  19. package/dist/ui/Callout.d.ts +27 -0
  20. package/dist/ui/Confetti.d.ts +46 -0
  21. package/dist/ui/CursorTrail.d.ts +25 -0
  22. package/dist/ui/DiffViewer.d.ts +33 -0
  23. package/dist/ui/Dock.d.ts +31 -0
  24. package/dist/ui/Globe.d.ts +44 -0
  25. package/dist/ui/Meteors.d.ts +23 -0
  26. package/dist/ui/ModelPicker.d.ts +38 -0
  27. package/dist/ui/ReasoningTrace.d.ts +28 -0
  28. package/dist/ui/Snippet.d.ts +19 -0
  29. package/dist/ui/SuggestionChips.d.ts +25 -0
  30. package/dist/ui/ToolCallTimeline.d.ts +34 -0
  31. package/dist/ui/UsageMeter.d.ts +26 -0
  32. package/dist/ui/WorldMap.d.ts +36 -0
  33. package/package.json +1 -1
  34. package/src/charts/BarList.tsx +83 -0
  35. package/src/charts/CategoryBar.tsx +95 -0
  36. package/src/charts/GaugeChart.tsx +175 -0
  37. package/src/charts/LineChart.tsx +264 -0
  38. package/src/charts/RadarChart.tsx +215 -0
  39. package/src/charts/StatusTracker.tsx +81 -0
  40. package/src/charts/index.ts +6 -0
  41. package/src/index.ts +31 -1
  42. package/src/ui/AnimatedBeam.tsx +187 -0
  43. package/src/ui/BentoGrid.tsx +89 -0
  44. package/src/ui/BorderBeam.tsx +96 -0
  45. package/src/ui/Callout.tsx +66 -0
  46. package/src/ui/Confetti.tsx +131 -0
  47. package/src/ui/CursorTrail.tsx +88 -0
  48. package/src/ui/DiffViewer.tsx +166 -0
  49. package/src/ui/Dock.tsx +124 -0
  50. package/src/ui/Globe.tsx +317 -0
  51. package/src/ui/Meteors.tsx +109 -0
  52. package/src/ui/ModelPicker.tsx +119 -0
  53. package/src/ui/ReasoningTrace.tsx +83 -0
  54. package/src/ui/Snippet.tsx +64 -0
  55. package/src/ui/SuggestionChips.tsx +65 -0
  56. package/src/ui/ToolCallTimeline.tsx +137 -0
  57. package/src/ui/UsageMeter.tsx +71 -0
  58. package/src/ui/WorldMap.tsx +191 -0
  59. package/dist/NumberInput-BQhVucw-.js +0 -491
  60. package/dist/cn-B6yFEsav.js +0 -8
@@ -0,0 +1,65 @@
1
+ // Wrapping row of dismissible/selectable pill chips (e.g. AI follow-up
2
+ // suggestions). Plain buttons in a flex-wrap row — no Kobalte primitive
3
+ // covers this shape, so it's hand-rolled like AnnouncementBar's tag chips.
4
+ import { X } from 'lucide-solid'
5
+ import type { JSX } from 'solid-js'
6
+ import { For, Show } from 'solid-js'
7
+
8
+ import { cn } from '../lib/cn'
9
+
10
+ export interface SuggestionChipsProps {
11
+ suggestions: string[]
12
+ onSelect?: (s: string) => void
13
+ onDismiss?: (s: string) => void
14
+ class?: string
15
+ }
16
+
17
+ /**
18
+ * Wrapping row of pill chips, e.g. AI-suggested follow-up prompts. Clicking a
19
+ * chip calls `onSelect`; if `onDismiss` is passed, each chip also shows a
20
+ * small "x" that removes it without triggering `onSelect`. The select and
21
+ * dismiss controls are real sibling `<button>`s (not nested — a `<button>`
22
+ * can't validly contain another interactive element), so both are reachable
23
+ * and operable independently via Tab/Enter/Space with no extra ARIA wiring.
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * <SuggestionChips
28
+ * suggestions={['Summarize this', 'Explain like I\'m 5', 'Write tests']}
29
+ * onSelect={(s) => sendMessage(s)}
30
+ * onDismiss={(s) => setSuggestions((prev) => prev.filter((x) => x !== s))}
31
+ * />
32
+ * ```
33
+ */
34
+ export function SuggestionChips(props: SuggestionChipsProps): JSX.Element {
35
+ return (
36
+ <div class={cn('flex flex-wrap gap-2', props.class)}>
37
+ <For each={props.suggestions}>
38
+ {(suggestion) => (
39
+ <span class="inline-flex items-center gap-1 rounded-full border border-border pl-3 pr-1.5 py-1.5 text-sm text-foreground transition-colors hover:bg-muted">
40
+ <button
41
+ type="button"
42
+ onClick={() => props.onSelect?.(suggestion)}
43
+ class="outline-none focus-visible:ring-2 focus-visible:ring-ring/30 rounded-sm"
44
+ >
45
+ {suggestion}
46
+ </button>
47
+ <Show when={props.onDismiss}>
48
+ <button
49
+ type="button"
50
+ aria-label={`Dismiss "${suggestion}"`}
51
+ onClick={(ev) => {
52
+ ev.stopPropagation()
53
+ props.onDismiss?.(suggestion)
54
+ }}
55
+ class="rounded-full p-0.5 text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/30"
56
+ >
57
+ <X class="h-3 w-3" />
58
+ </button>
59
+ </Show>
60
+ </span>
61
+ )}
62
+ </For>
63
+ </div>
64
+ )
65
+ }
@@ -0,0 +1,137 @@
1
+ // ToolCallTimeline — vertical timeline (à la Timeline.tsx's connector line +
2
+ // dot idiom) where each node is an AI tool call, with a Collapse-style
3
+ // details panel for args/result.
4
+ import { Check, Loader2, X } from 'lucide-solid'
5
+ import { type JSX, For, Show, createSignal } from 'solid-js'
6
+
7
+ import { cn } from '../lib/cn'
8
+
9
+ /** Status of a single tool call rendered by {@link ToolCallTimeline}. */
10
+ export type ToolCallStatus = 'pending' | 'success' | 'error'
11
+
12
+ /** A single tool invocation rendered by {@link ToolCallTimeline}. */
13
+ export interface ToolCall {
14
+ /** Tool/function name, rendered in mono. */
15
+ name: string
16
+ status: ToolCallStatus
17
+ /** Arguments passed to the call. Shown in a collapsible details area when present. */
18
+ args?: JSX.Element | string
19
+ /** Result returned by the call. Shown in a collapsible details area when present. */
20
+ result?: JSX.Element | string
21
+ }
22
+
23
+ export interface ToolCallTimelineProps {
24
+ calls: ToolCall[]
25
+ class?: string
26
+ }
27
+
28
+ /**
29
+ * Vertical timeline of AI tool calls: a connector line runs down the left with
30
+ * a status node per call (spinner while `pending`, check when `success`, x when
31
+ * `error`), the tool name in mono, and — when `args`/`result` are given — a
32
+ * collapsible details panel underneath.
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * <ToolCallTimeline
37
+ * calls={[
38
+ * { name: 'search_docs', status: 'success', args: 'query: "refunds"', result: '3 matches' },
39
+ * { name: 'update_ticket', status: 'pending' },
40
+ * ]}
41
+ * />
42
+ * ```
43
+ */
44
+ export function ToolCallTimeline(props: ToolCallTimelineProps): JSX.Element {
45
+ return (
46
+ <ol class={cn('relative flex flex-col gap-4', props.class)}>
47
+ <For each={props.calls}>
48
+ {(call, index) => (
49
+ <li class="relative flex gap-3 pl-1">
50
+ <Show when={index() < props.calls.length - 1}>
51
+ <span
52
+ aria-hidden="true"
53
+ class="absolute left-[calc(0.5rem+2px)] top-6 -bottom-4 w-px -translate-x-1/2 bg-border"
54
+ />
55
+ </Show>
56
+ <StatusNode status={call.status} />
57
+ <div class="min-w-0 flex-1 pb-0.5">
58
+ <p class="pt-0.5 font-mono text-sm text-foreground">{call.name}</p>
59
+ <Show when={call.args !== undefined || call.result !== undefined}>
60
+ <ToolCallDetails args={call.args} result={call.result} />
61
+ </Show>
62
+ </div>
63
+ </li>
64
+ )}
65
+ </For>
66
+ </ol>
67
+ )
68
+ }
69
+
70
+ const STATUS_TONE: Record<ToolCallStatus, string> = {
71
+ pending: 'border-border bg-muted text-muted-foreground',
72
+ success: 'border-emerald-500/30 bg-emerald-500/15 text-emerald-500',
73
+ error: 'border-destructive/30 bg-destructive/15 text-destructive',
74
+ }
75
+
76
+ function StatusNode(props: { status: ToolCallStatus }): JSX.Element {
77
+ return (
78
+ <span
79
+ aria-hidden="true"
80
+ class={cn(
81
+ 'relative z-[1] mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full border',
82
+ STATUS_TONE[props.status],
83
+ )}
84
+ >
85
+ <Show when={props.status === 'pending'}>
86
+ <Loader2 class="h-3 w-3 animate-spin motion-reduce:animate-none" />
87
+ </Show>
88
+ <Show when={props.status === 'success'}>
89
+ <Check class="h-3 w-3" />
90
+ </Show>
91
+ <Show when={props.status === 'error'}>
92
+ <X class="h-3 w-3" />
93
+ </Show>
94
+ </span>
95
+ )
96
+ }
97
+
98
+ // Reuses Collapse.tsx's grid-rows transition idiom for the args/result panel.
99
+ function ToolCallDetails(props: { args?: JSX.Element | string; result?: JSX.Element | string }): JSX.Element {
100
+ const [open, setOpen] = createSignal(false)
101
+
102
+ return (
103
+ <div class="mt-1">
104
+ <button
105
+ type="button"
106
+ aria-expanded={open()}
107
+ onClick={() => setOpen(!open())}
108
+ class="text-xs font-medium text-muted-foreground hover:text-foreground"
109
+ >
110
+ {open() ? 'Hide details' : 'Show details'}
111
+ </button>
112
+ <div
113
+ class={cn(
114
+ 'grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none',
115
+ open() ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',
116
+ )}
117
+ >
118
+ <div class="overflow-hidden">
119
+ <div class="mt-1.5 space-y-1.5 rounded-lg border border-border bg-muted/40 p-2 text-xs text-muted-foreground">
120
+ <Show when={props.args !== undefined}>
121
+ <div>
122
+ <p class="font-medium text-foreground/80">Args</p>
123
+ <p class="whitespace-pre-wrap font-mono">{props.args}</p>
124
+ </div>
125
+ </Show>
126
+ <Show when={props.result !== undefined}>
127
+ <div>
128
+ <p class="font-medium text-foreground/80">Result</p>
129
+ <p class="whitespace-pre-wrap font-mono">{props.result}</p>
130
+ </div>
131
+ </Show>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ )
137
+ }
@@ -0,0 +1,71 @@
1
+ // Labeled consumption bar (e.g. token/quota usage) on Kobalte's Meter
2
+ // primitive — same idiom as Meter.tsx/Progress.tsx, plus a warning-tone fill
3
+ // past a configurable threshold and a "N left" remaining line.
4
+ import { Meter as KMeter } from '@kobalte/core/meter'
5
+ import type { JSX } from 'solid-js'
6
+ import { Show } from 'solid-js'
7
+
8
+ import { cn } from '../lib/cn'
9
+
10
+ export interface UsageMeterProps {
11
+ used: number
12
+ limit: number
13
+ /** Header content shown before the "{used}/{limit}{unit}" count. */
14
+ label?: JSX.Element
15
+ /** Appended to displayed numbers, e.g. `' tokens'` or `'%'`. */
16
+ unit?: string
17
+ /** Fraction of `limit` (0–1) at which the fill switches to the warning tone. @default 0.85 */
18
+ warnAt?: number
19
+ class?: string
20
+ }
21
+
22
+ /**
23
+ * Labeled consumption bar: a header row with `label` and the raw
24
+ * "{used}/{limit}{unit}" count, a track whose fill width is `used / limit`
25
+ * (clamped to 0–100%) and switches to a warning tone once that ratio reaches
26
+ * `warnAt`, and a small "{remaining} left" line underneath. Built on Kobalte's
27
+ * `Meter` primitive, so it carries the same `role="meter"` a11y semantics as
28
+ * {@link Meter}.
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * <UsageMeter used={92} limit={100} label="API requests" unit=" req" warnAt={0.9} />
33
+ * ```
34
+ */
35
+ export function UsageMeter(props: UsageMeterProps): JSX.Element {
36
+ const unit = () => props.unit ?? ''
37
+ const ratio = () => (props.limit > 0 ? props.used / props.limit : 0)
38
+ const percent = () => Math.max(0, Math.min(100, ratio() * 100))
39
+ const isWarning = () => ratio() >= (props.warnAt ?? 0.85)
40
+ const remaining = () => Math.max(0, props.limit - props.used)
41
+
42
+ return (
43
+ <KMeter
44
+ value={props.used}
45
+ minValue={0}
46
+ maxValue={props.limit}
47
+ getValueLabel={() => `${props.used}/${props.limit}${unit()}`}
48
+ class={cn('flex flex-col gap-1.5', props.class)}
49
+ >
50
+ <div class="flex items-center justify-between text-sm text-foreground">
51
+ <Show when={props.label}>
52
+ <KMeter.Label>{props.label}</KMeter.Label>
53
+ </Show>
54
+ <KMeter.ValueLabel class="ml-auto tabular-nums text-muted-foreground" />
55
+ </div>
56
+ <KMeter.Track class="h-2 overflow-hidden rounded-full bg-muted">
57
+ <KMeter.Fill
58
+ class={cn(
59
+ 'h-full rounded-full transition-all duration-500',
60
+ isWarning() ? 'bg-amber-500' : 'bg-primary',
61
+ )}
62
+ style={{ width: `${percent()}%` }}
63
+ />
64
+ </KMeter.Track>
65
+ <p class="text-xs text-muted-foreground">
66
+ {remaining()}
67
+ {unit()} left
68
+ </p>
69
+ </KMeter>
70
+ )
71
+ }
@@ -0,0 +1,191 @@
1
+ // WorldMap — a lightweight, self-contained SVG "map": a dotted-grid backdrop
2
+ // (no coastlines, no geojson/asset) with connection arcs projected from
3
+ // lat/lng via simple equirectangular mapping. Each arc bulges upward and
4
+ // carries a traveling primary→accent gradient segment, reusing
5
+ // AnimatedBeam's "moving gradient stops along a quadratic curve" technique.
6
+ // Arcs render static (no travel) under reduced motion.
7
+ import { createUniqueId, For, onCleanup, type JSX } from 'solid-js'
8
+
9
+ import { cn } from '../lib/cn'
10
+ import { animate, motionReduced } from '../lib/motion'
11
+
12
+ export interface WorldMapPoint {
13
+ lat: number
14
+ lng: number
15
+ label?: string
16
+ }
17
+
18
+ export interface WorldMapConnection {
19
+ from: WorldMapPoint
20
+ to: WorldMapPoint
21
+ }
22
+
23
+ export interface WorldMapProps {
24
+ /** Arcs to draw between projected points. @default [] */
25
+ connections?: WorldMapConnection[]
26
+ class?: string
27
+ }
28
+
29
+ const WIDTH = 800
30
+ const HEIGHT = 400
31
+ /** Px spacing between backdrop grid dots. */
32
+ const GRID_GAP = 16
33
+
34
+ interface Point {
35
+ x: number
36
+ y: number
37
+ }
38
+
39
+ /** Equirectangular projection: lat/lng -> SVG coordinates in the WIDTH x HEIGHT viewBox. */
40
+ function project(point: WorldMapPoint): Point {
41
+ return {
42
+ x: ((point.lng + 180) / 360) * WIDTH,
43
+ y: ((90 - point.lat) / 180) * HEIGHT,
44
+ }
45
+ }
46
+
47
+ /** Point at parameter `t` (0..1) along a quadratic Bezier curve. */
48
+ function pointAt(start: Point, control: Point, end: Point, t: number): Point {
49
+ const it = 1 - t
50
+ return {
51
+ x: it * it * start.x + 2 * it * t * control.x + t * t * end.x,
52
+ y: it * it * start.y + 2 * it * t * control.y + t * t * end.y,
53
+ }
54
+ }
55
+
56
+ /** Centers of an evenly spaced dot grid covering the WIDTH x HEIGHT viewBox. */
57
+ function gridDots(): Point[] {
58
+ const dots: Point[] = []
59
+ for (let y = GRID_GAP / 2; y < HEIGHT; y += GRID_GAP) {
60
+ for (let x = GRID_GAP / 2; x < WIDTH; x += GRID_GAP) {
61
+ dots.push({ x, y })
62
+ }
63
+ }
64
+ return dots
65
+ }
66
+
67
+ interface ConnectionArcProps {
68
+ connection: WorldMapConnection
69
+ }
70
+
71
+ /** One arc: a faint static base stroke plus (unless reduced motion) a traveling gradient segment. */
72
+ function ConnectionArc(props: ConnectionArcProps): JSX.Element {
73
+ const gradientId = `world-map-beam-${createUniqueId()}`
74
+ const start = project(props.connection.from)
75
+ const end = project(props.connection.to)
76
+ const mx = (start.x + end.x) / 2
77
+ const my = (start.y + end.y) / 2
78
+ // Bulge upward (smaller y) regardless of endpoint order.
79
+ const bulge = Math.max(20, Math.hypot(end.x - start.x, end.y - start.y) * 0.25)
80
+ const control = { x: mx, y: my - bulge }
81
+ const d = `M ${start.x} ${start.y} Q ${control.x} ${control.y} ${end.x} ${end.y}`
82
+ const reduced = motionReduced()
83
+
84
+ let gradientEl: SVGLinearGradientElement | undefined
85
+
86
+ if (!reduced) {
87
+ const segment = 0.2
88
+ const controls = animate(0, 1, {
89
+ duration: 3,
90
+ repeat: Infinity,
91
+ ease: 'linear',
92
+ onUpdate: (p: number) => {
93
+ const from = pointAt(start, control, end, Math.max(0, p - segment))
94
+ const to = pointAt(start, control, end, p)
95
+ gradientEl?.setAttribute('x1', String(from.x))
96
+ gradientEl?.setAttribute('y1', String(from.y))
97
+ gradientEl?.setAttribute('x2', String(to.x))
98
+ gradientEl?.setAttribute('y2', String(to.y))
99
+ },
100
+ })
101
+ onCleanup(() => controls.stop())
102
+ }
103
+
104
+ return (
105
+ <>
106
+ <path d={d} stroke="hsl(var(--muted-foreground))" stroke-width="1" stroke-opacity="0.3" fill="none" />
107
+ {!reduced && (
108
+ <>
109
+ <defs>
110
+ <linearGradient
111
+ ref={(el) => {
112
+ gradientEl = el
113
+ }}
114
+ id={gradientId}
115
+ gradientUnits="userSpaceOnUse"
116
+ >
117
+ <stop offset="0%" stop-color="hsl(var(--primary))" stop-opacity="0" />
118
+ <stop offset="50%" stop-color="hsl(var(--primary))" />
119
+ <stop offset="50%" stop-color="hsl(var(--accent))" />
120
+ <stop offset="100%" stop-color="hsl(var(--accent))" stop-opacity="0" />
121
+ </linearGradient>
122
+ </defs>
123
+ <path d={d} stroke={`url(#${gradientId})`} stroke-width="1.5" stroke-linecap="round" fill="none" />
124
+ </>
125
+ )}
126
+ <circle
127
+ cx={start.x}
128
+ cy={start.y}
129
+ r="2.5"
130
+ fill="hsl(var(--primary))"
131
+ class={reduced ? undefined : 'animate-pulse'}
132
+ />
133
+ <circle
134
+ cx={end.x}
135
+ cy={end.y}
136
+ r="2.5"
137
+ fill="hsl(var(--primary))"
138
+ class={reduced ? undefined : 'animate-pulse'}
139
+ />
140
+ </>
141
+ )
142
+ }
143
+
144
+ /**
145
+ * A decorative, dependency-free SVG "world map": an evenly spaced dotted-grid
146
+ * backdrop (a stylized map, not accurate coastlines) with connection arcs
147
+ * projected from lat/lng via equirectangular mapping. Each arc bulges upward
148
+ * and carries a traveling primary→accent gradient segment (reusing
149
+ * {@link AnimatedBeam}'s technique); arcs render static under reduced motion.
150
+ * Responsive — scales to its container via the 2:1 `viewBox`.
151
+ *
152
+ * @example
153
+ * ```tsx
154
+ * <WorldMap
155
+ * class="w-full"
156
+ * connections={[
157
+ * { from: { lat: 40.7128, lng: -74.006, label: 'New York' }, to: { lat: 51.5074, lng: -0.1278, label: 'London' } },
158
+ * { from: { lat: 51.5074, lng: -0.1278 }, to: { lat: 35.6762, lng: 139.6503, label: 'Tokyo' } },
159
+ * { from: { lat: 40.7128, lng: -74.006 }, to: { lat: -23.5505, lng: -46.6333, label: 'São Paulo' } },
160
+ * ]}
161
+ * />
162
+ * ```
163
+ */
164
+ export function WorldMap(props: WorldMapProps): JSX.Element {
165
+ const connections = () => props.connections ?? []
166
+ const label = () =>
167
+ connections()
168
+ .map(
169
+ (c) =>
170
+ `${c.from.label ?? `${c.from.lat},${c.from.lng}`} to ${c.to.label ?? `${c.to.lat},${c.to.lng}`}`,
171
+ )
172
+ .join('; ')
173
+
174
+ return (
175
+ <svg
176
+ role={connections().length > 0 ? 'img' : undefined}
177
+ aria-label={connections().length > 0 ? `World map with connections: ${label()}` : undefined}
178
+ aria-hidden={connections().length > 0 ? undefined : 'true'}
179
+ class={cn('w-full', props.class)}
180
+ viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
181
+ fill="none"
182
+ >
183
+ <For each={gridDots()}>
184
+ {(dot) => (
185
+ <circle cx={dot.x} cy={dot.y} r="0.8" fill="hsl(var(--muted-foreground))" fill-opacity="0.25" />
186
+ )}
187
+ </For>
188
+ <For each={connections()}>{(connection) => <ConnectionArc connection={connection} />}</For>
189
+ </svg>
190
+ )
191
+ }