@a4ui/core 0.37.1 → 0.38.1

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 (105) hide show
  1. package/README.md +8 -7
  2. package/dist/Collapse-Cr5cDqAl.js +375 -0
  3. package/dist/charts/AreaChart.d.ts +38 -0
  4. package/dist/charts/BarChart.d.ts +17 -1
  5. package/dist/charts/PieChart.d.ts +29 -0
  6. package/dist/charts/RadialBarChart.d.ts +33 -0
  7. package/dist/charts/index.d.ts +5 -0
  8. package/dist/charts/shared.d.ts +111 -0
  9. package/dist/charts.js +1493 -780
  10. package/dist/commerce.js +625 -634
  11. package/dist/elements.css +1076 -8
  12. package/dist/elements.iife.js +1 -6
  13. package/dist/elements.js +7774 -9133
  14. package/dist/full.css +1076 -8
  15. package/dist/index.d.ts +30 -3
  16. package/dist/index.js +17502 -15126
  17. package/dist/layout/Aurora.d.ts +16 -0
  18. package/dist/layout/EffectsToggle.d.ts +1 -1
  19. package/dist/layout/SpaceBackground.d.ts +1 -1
  20. package/dist/motion-CTIBwMCA.js +104 -0
  21. package/dist/styles.css +110 -3
  22. package/dist/ui/AspectRatio.d.ts +19 -0
  23. package/dist/ui/Attachment.d.ts +70 -0
  24. package/dist/ui/ButtonGroup.d.ts +37 -0
  25. package/dist/ui/ChatMarker.d.ts +20 -0
  26. package/dist/ui/ChatThread.d.ts +32 -8
  27. package/dist/ui/ContactSection.d.ts +49 -0
  28. package/dist/ui/CtaBanner.d.ts +28 -0
  29. package/dist/ui/Drawer.d.ts +5 -2
  30. package/dist/ui/Faq.d.ts +29 -0
  31. package/dist/ui/FeatureGrid.d.ts +35 -0
  32. package/dist/ui/Field.d.ts +148 -0
  33. package/dist/ui/Hero.d.ts +48 -0
  34. package/dist/ui/InputGroup.d.ts +93 -0
  35. package/dist/ui/Item.d.ts +63 -0
  36. package/dist/ui/Label.d.ts +23 -0
  37. package/dist/ui/Menubar.d.ts +83 -0
  38. package/dist/ui/Message.d.ts +51 -5
  39. package/dist/ui/Modal.d.ts +8 -2
  40. package/dist/ui/NativeSelect.d.ts +26 -0
  41. package/dist/ui/NavigationMenu.d.ts +85 -0
  42. package/dist/ui/PromptComposer.d.ts +5 -0
  43. package/dist/ui/Prose.d.ts +21 -0
  44. package/dist/ui/Questionnaire.d.ts +71 -0
  45. package/dist/ui/ScrollArea.d.ts +30 -0
  46. package/dist/ui/Sidebar.d.ts +229 -0
  47. package/dist/ui/SiteFooter.d.ts +39 -0
  48. package/dist/ui/SiteNav.d.ts +35 -0
  49. package/dist/ui/TeamGrid.d.ts +33 -0
  50. package/dist/ui/Testimonials.d.ts +37 -0
  51. package/dist/ui/internal/sectionKit.d.ts +22 -0
  52. package/dist/ui/internal/socialIcons.d.ts +9 -0
  53. package/package.json +38 -26
  54. package/preset.d.ts +23 -0
  55. package/src/charts/AreaChart.tsx +206 -0
  56. package/src/charts/BarChart.tsx +241 -5
  57. package/src/charts/DonutChart.tsx +8 -11
  58. package/src/charts/LineChart.tsx +2 -3
  59. package/src/charts/PieChart.tsx +145 -0
  60. package/src/charts/RadialBarChart.tsx +141 -0
  61. package/src/charts/index.ts +14 -0
  62. package/src/charts/shared.tsx +304 -0
  63. package/src/index.ts +192 -3
  64. package/src/layout/Aurora.tsx +36 -7
  65. package/src/layout/sceneEffects.ts +22 -3
  66. package/src/ui/AlertDialog.tsx +6 -3
  67. package/src/ui/AspectRatio.tsx +35 -0
  68. package/src/ui/Attachment.tsx +189 -0
  69. package/src/ui/ButtonGroup.tsx +89 -0
  70. package/src/ui/ChatMarker.tsx +58 -0
  71. package/src/ui/ChatThread.tsx +115 -23
  72. package/src/ui/Command.tsx +6 -3
  73. package/src/ui/ContactSection.tsx +199 -0
  74. package/src/ui/CtaBanner.tsx +62 -0
  75. package/src/ui/DateField.tsx +2 -1
  76. package/src/ui/DateRangePicker.tsx +2 -1
  77. package/src/ui/Drawer.tsx +43 -7
  78. package/src/ui/Faq.tsx +57 -0
  79. package/src/ui/FeatureGrid.tsx +77 -0
  80. package/src/ui/Field.tsx +374 -0
  81. package/src/ui/Hero.tsx +146 -0
  82. package/src/ui/Image.tsx +6 -3
  83. package/src/ui/InputGroup.tsx +219 -0
  84. package/src/ui/Item.tsx +185 -0
  85. package/src/ui/Label.tsx +50 -0
  86. package/src/ui/Menubar.tsx +212 -0
  87. package/src/ui/Message.tsx +139 -13
  88. package/src/ui/Modal.tsx +47 -8
  89. package/src/ui/MultiSelect.tsx +2 -1
  90. package/src/ui/NativeSelect.tsx +66 -0
  91. package/src/ui/NavigationMenu.tsx +262 -0
  92. package/src/ui/PromptComposer.tsx +11 -0
  93. package/src/ui/Prose.tsx +63 -0
  94. package/src/ui/Questionnaire.tsx +331 -0
  95. package/src/ui/ScrollArea.tsx +70 -0
  96. package/src/ui/Sidebar.tsx +753 -0
  97. package/src/ui/SiteFooter.tsx +112 -0
  98. package/src/ui/SiteNav.tsx +146 -0
  99. package/src/ui/TeamGrid.tsx +81 -0
  100. package/src/ui/Testimonials.tsx +103 -0
  101. package/src/ui/TimeField.tsx +2 -1
  102. package/src/ui/internal/sectionKit.tsx +72 -0
  103. package/src/ui/internal/socialIcons.tsx +41 -0
  104. package/dist/NumberInput-Bnh2u7pp.js +0 -381
  105. package/dist/motion-jYMWmZqB.js +0 -119
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  // import '@a4ui/core/styles.css'
9
9
  // import { Button, Card, Modal } from '@a4ui/core'
10
10
 
11
- export const A4UI_VERSION = '0.37.1'
11
+ export const A4UI_VERSION = '0.38.1'
12
12
 
13
13
  // Helpers (src/lib) — generic, framework-level utilities.
14
14
  export { cn } from './lib/cn'
@@ -42,6 +42,19 @@ export { createLinkedSelection, type LinkedSelection } from './lib/createLinkedS
42
42
  export { Accordion, type AccordionItem } from './ui/Accordion'
43
43
  export { Badge, type BadgeTone } from './ui/Badge'
44
44
  export { Button, type ButtonVariant } from './ui/Button'
45
+ export {
46
+ ButtonGroup,
47
+ ButtonGroupText,
48
+ ButtonGroupSeparator,
49
+ type ButtonGroupProps,
50
+ type ButtonGroupTextProps,
51
+ type ButtonGroupSeparatorProps,
52
+ type ButtonGroupOrientation,
53
+ } from './ui/ButtonGroup'
54
+ export { AspectRatio, type AspectRatioProps } from './ui/AspectRatio'
55
+ export { Label, type LabelProps } from './ui/Label'
56
+ export { NativeSelect, type NativeSelectProps, type NativeSelectSize } from './ui/NativeSelect'
57
+ export { Prose, type ProseProps, type ProseSize } from './ui/Prose'
45
58
  export { Card, CardHeader, CardTitle, CardContent } from './ui/Card'
46
59
  export { Checkbox } from './ui/Checkbox'
47
60
  export { DateField } from './ui/DateField'
@@ -49,12 +62,55 @@ export { TimeField, type TimeFieldProps } from './ui/TimeField'
49
62
  export { DateTimeField, type DateTimeFieldProps } from './ui/DateTimeField'
50
63
  export { DateRangePicker, type DateRange, type DateRangePickerProps } from './ui/DateRangePicker'
51
64
  export { FormField, FormLabel, FormControl, FormDescription, FormError, type FormFieldProps } from './ui/Form'
65
+ export {
66
+ Field,
67
+ FieldSet,
68
+ FieldLegend,
69
+ FieldGroup,
70
+ FieldContent,
71
+ FieldLabel,
72
+ FieldTitle,
73
+ FieldDescription,
74
+ FieldSeparator,
75
+ FieldError,
76
+ type FieldProps,
77
+ type FieldSetProps,
78
+ type FieldLegendProps,
79
+ type FieldGroupProps,
80
+ type FieldLabelProps,
81
+ type FieldSeparatorProps,
82
+ type FieldErrorProps,
83
+ type FieldOrientation,
84
+ } from './ui/Field'
85
+ export {
86
+ InputGroup,
87
+ InputGroupAddon,
88
+ InputGroupText,
89
+ InputGroupButton,
90
+ InputGroupInput,
91
+ InputGroupTextarea,
92
+ type InputGroupProps,
93
+ type InputGroupAddonProps,
94
+ type InputGroupAddonAlign,
95
+ type InputGroupButtonProps,
96
+ type InputGroupButtonSize,
97
+ type InputGroupInputProps,
98
+ type InputGroupTextareaProps,
99
+ } from './ui/InputGroup'
52
100
  export { FileUpload, type UploadFile, type FileUploadProps } from './ui/FileUpload'
53
101
  export { MultiSelect, type MultiSelectOption, type MultiSelectProps } from './ui/MultiSelect'
54
- export { Drawer } from './ui/Drawer'
102
+ export { Drawer, type DrawerProps, type DrawerSide } from './ui/Drawer'
55
103
  export { Dropdown, type DropdownItem } from './ui/Dropdown'
56
104
  export { Dropzone } from './ui/Dropzone'
57
105
  export { Input } from './ui/Input'
106
+ export {
107
+ Questionnaire,
108
+ type QuestionnaireProps,
109
+ type QuestionnaireQuestion,
110
+ type QuestionnaireOption,
111
+ type QuestionnaireAnswers,
112
+ type QuestionnaireLabels,
113
+ } from './ui/Questionnaire'
58
114
  export { Modal } from './ui/Modal'
59
115
  export { PageHeader } from './ui/PageHeader'
60
116
  export { Pagination } from './ui/Pagination'
@@ -84,6 +140,37 @@ export { Toggle } from './ui/Toggle'
84
140
  export { ToggleGroup, type ToggleGroupOption } from './ui/ToggleGroup'
85
141
  export { SegmentedControl, type SegmentedOption } from './ui/SegmentedControl'
86
142
  export { Breadcrumb, type BreadcrumbItem } from './ui/Breadcrumb'
143
+ export {
144
+ Menubar,
145
+ MenubarCheckboxItem,
146
+ MenubarItem,
147
+ MenubarLabel,
148
+ MenubarMenu,
149
+ MenubarRadioGroup,
150
+ MenubarRadioItem,
151
+ MenubarSeparator,
152
+ MenubarSub,
153
+ type MenubarCheckboxItemProps,
154
+ type MenubarItemProps,
155
+ type MenubarMenuProps,
156
+ type MenubarProps,
157
+ type MenubarRadioGroupProps,
158
+ type MenubarRadioItemProps,
159
+ type MenubarSubProps,
160
+ } from './ui/Menubar'
161
+ export {
162
+ NavigationMenu,
163
+ NavigationMenuContent,
164
+ NavigationMenuItem,
165
+ NavigationMenuLink,
166
+ NavigationMenuPanelLink,
167
+ NavigationMenuTrigger,
168
+ type NavigationMenuContentProps,
169
+ type NavigationMenuLinkProps,
170
+ type NavigationMenuMobileSection,
171
+ type NavigationMenuPanelLinkProps,
172
+ type NavigationMenuProps,
173
+ } from './ui/NavigationMenu'
87
174
  export { AlertDialog } from './ui/AlertDialog'
88
175
  export { ContextMenu, type ContextMenuItem } from './ui/ContextMenu'
89
176
  export { Combobox } from './ui/Combobox'
@@ -92,6 +179,33 @@ export { Stepper, type StepItem, type StepperProps } from './ui/Stepper'
92
179
  export { Timeline, type TimelineItem, type TimelineTone, type TimelineProps } from './ui/Timeline'
93
180
  export { Rating, type RatingProps } from './ui/Rating'
94
181
  export { PricingTable, type PricingTier, type PricingPeriod } from './ui/PricingTable'
182
+ // Page sections — data-driven marketing blocks.
183
+ export type { SectionLink } from './ui/internal/sectionKit'
184
+ export { SiteNav, type SiteNavProps } from './ui/SiteNav'
185
+ export { Hero, type HeroProps, type HeroLayout, type HeroBackground } from './ui/Hero'
186
+ export { FeatureGrid, type FeatureGridProps, type FeatureItem } from './ui/FeatureGrid'
187
+ export {
188
+ Testimonials,
189
+ type TestimonialsProps,
190
+ type TestimonialItem,
191
+ type TestimonialsLayout,
192
+ } from './ui/Testimonials'
193
+ export { Faq, type FaqProps, type FaqItem } from './ui/Faq'
194
+ export { CtaBanner, type CtaBannerProps } from './ui/CtaBanner'
195
+ export {
196
+ ContactSection,
197
+ type ContactSectionProps,
198
+ type ContactField,
199
+ type ContactValues,
200
+ } from './ui/ContactSection'
201
+ export {
202
+ SiteFooter,
203
+ type SiteFooterProps,
204
+ type FooterColumn,
205
+ type SocialLink,
206
+ type SocialNetwork,
207
+ } from './ui/SiteFooter'
208
+ export { TeamGrid, type TeamGridProps, type TeamMember } from './ui/TeamGrid'
95
209
  export { BeforeAfter, type BeforeAfterProps } from './ui/BeforeAfter'
96
210
  export {
97
211
  Configurator,
@@ -124,6 +238,23 @@ export {
124
238
  export { Anchor, type AnchorItem, type AnchorProps } from './ui/Anchor'
125
239
  export { Highlight, type HighlightProps } from './ui/Highlight'
126
240
  export { List, type ListItem } from './ui/List'
241
+ export {
242
+ Item,
243
+ ItemActions,
244
+ ItemContent,
245
+ ItemDescription,
246
+ ItemFooter,
247
+ ItemGroup,
248
+ ItemHeader,
249
+ ItemMedia,
250
+ ItemSeparator,
251
+ ItemTitle,
252
+ type ItemMediaVariant,
253
+ type ItemProps,
254
+ type ItemSize,
255
+ type ItemVariant,
256
+ } from './ui/Item'
257
+ export { ScrollArea, type ScrollAreaOrientation, type ScrollAreaProps } from './ui/ScrollArea'
127
258
  export { Countdown, type CountdownProps } from './ui/Countdown'
128
259
  export { Affix } from './ui/Affix'
129
260
  export { Transfer, type TransferItem, type TransferProps } from './ui/Transfer'
@@ -151,7 +282,31 @@ export { Clock, type ClockProps } from './ui/Clock'
151
282
 
152
283
  // Conversation / AI, transitions, and depth surfaces (roadmap phase 1).
153
284
  export { ChatThread, type ChatThreadProps } from './ui/ChatThread'
154
- export { Message, type MessageProps, type ChatRole } from './ui/Message'
285
+ export {
286
+ Message,
287
+ MessageGroup,
288
+ type MessageProps,
289
+ type MessageGroupProps,
290
+ type MessageVariant,
291
+ type MessageAlign,
292
+ type ChatRole,
293
+ } from './ui/Message'
294
+ export { type ChatThreadApi } from './ui/ChatThread'
295
+ export { ChatMarker, type ChatMarkerProps, type ChatMarkerVariant } from './ui/ChatMarker'
296
+ export {
297
+ Attachment,
298
+ AttachmentMedia,
299
+ AttachmentContent,
300
+ AttachmentTitle,
301
+ AttachmentDescription,
302
+ AttachmentActions,
303
+ AttachmentAction,
304
+ AttachmentGroup,
305
+ type AttachmentProps,
306
+ type AttachmentActionProps,
307
+ type AttachmentState,
308
+ type AttachmentOrientation,
309
+ } from './ui/Attachment'
155
310
  export { StreamingText, type StreamingTextProps } from './ui/StreamingText'
156
311
  export { Citation, type CitationProps, SourceList, type SourceListProps } from './ui/Citation'
157
312
  export { PromptComposer, type PromptComposerProps } from './ui/PromptComposer'
@@ -330,8 +485,42 @@ export { flyToCart, type FlyToCartOptions } from './lib/flyToCart'
330
485
  // Layout (src/layout) — generic shell + backdrop + toggles. App-coupled pieces
331
486
  // (Sidebar, Topbar, CompanySwitcher, DemoBanner, CommandPalette) stay in the app.
332
487
  export { AppShell } from './layout/AppShell'
488
+ export {
489
+ Sidebar,
490
+ SidebarProvider,
491
+ SidebarTrigger,
492
+ SidebarRail,
493
+ SidebarInset,
494
+ SidebarHeader,
495
+ SidebarFooter,
496
+ SidebarContent,
497
+ SidebarGroup,
498
+ SidebarGroupLabel,
499
+ SidebarGroupAction,
500
+ SidebarGroupContent,
501
+ SidebarMenu,
502
+ SidebarMenuItem,
503
+ SidebarMenuButton,
504
+ SidebarMenuAction,
505
+ SidebarMenuBadge,
506
+ SidebarMenuSub,
507
+ SidebarMenuSubItem,
508
+ SidebarMenuSubButton,
509
+ SidebarMenuSkeleton,
510
+ useSidebar,
511
+ type SidebarProps,
512
+ type SidebarProviderProps,
513
+ type SidebarTriggerProps,
514
+ type SidebarGroupActionProps,
515
+ type SidebarMenuButtonProps,
516
+ type SidebarMenuSubProps,
517
+ type SidebarSide,
518
+ type SidebarVariant,
519
+ type SidebarCollapsible,
520
+ } from './ui/Sidebar'
333
521
  export { SpaceBackground } from './layout/SpaceBackground'
334
522
  export { Aurora, type AuroraProps } from './layout/Aurora'
523
+ export type { ModalProps } from './ui/Modal'
335
524
  export { ThemedScenery, type ThemedSceneryProps } from './layout/ThemedScenery'
336
525
  export { SnowScenery } from './layout/SnowScenery'
337
526
  export { ChristmasBackground } from './layout/ChristmasBackground'
@@ -30,9 +30,32 @@ export interface AuroraProps {
30
30
  * @default 'blobs'
31
31
  */
32
32
  variant?: 'blobs' | 'mesh'
33
+ /**
34
+ * Blob palette. Each entry is a CSS color (`'#ff6a3d'`, `'oklch(0.7 0.2 40)'`,
35
+ * `'hsl(var(--brand))'`) or a theme token written as a custom property name
36
+ * (`'--primary'`, `'--accent'`, `'--success'` ...). Entries are assigned to the
37
+ * blobs / mesh points in order and repeat when there are fewer colors than
38
+ * blobs. Defaults to the theme tokens `--primary` / `--accent`, so every theme
39
+ * still tints it for free.
40
+ */
41
+ colors?: string[]
42
+ /**
43
+ * Color of the cursor-following glow, same format as a `colors` entry.
44
+ * @default '--primary'
45
+ */
46
+ glowColor?: string
47
+ /** Peak opacity of the cursor glow, 0..1. @default 0.1 */
48
+ glowOpacity?: number
33
49
  class?: string
34
50
  }
35
51
 
52
+ /** Resolve a palette entry to a CSS color at the given alpha. `--token` => theme token. */
53
+ function colorAt(color: string, alpha: number): string {
54
+ const a = Math.min(1, Math.max(0, alpha))
55
+ if (color.startsWith('--')) return `hsl(var(${color}) / ${a.toFixed(2)})`
56
+ return `color-mix(in srgb, ${color} ${(a * 100).toFixed(0)}%, transparent)`
57
+ }
58
+
36
59
  // Mesh variant — several token-tinted radial gradients composited into one
37
60
  // backdrop (positions chosen to fill the corners without a hard seam).
38
61
  const MESH_STOPS = [
@@ -68,10 +91,16 @@ const BLOBS = [
68
91
  */
69
92
  export function Aurora(props: AuroraProps): JSX.Element {
70
93
  const intensity = (): number => props.intensity ?? 0.45
94
+ // Palette entry for blob/mesh index `i`: custom colors cycle; otherwise the
95
+ // built-in token for that slot.
96
+ const colorFor = (i: number, token: string): string => {
97
+ const list = props.colors
98
+ return list && list.length > 0 ? list[i % list.length] : token
99
+ }
71
100
  const meshBg = (): string =>
72
101
  MESH_STOPS.map(
73
- (s) =>
74
- `radial-gradient(45% 45% at ${s.at}, hsl(var(${s.token}) / ${(intensity() * s.a).toFixed(2)}), transparent 70%)`,
102
+ (s, i) =>
103
+ `radial-gradient(45% 45% at ${s.at}, ${colorAt(colorFor(i, s.token), intensity() * s.a)}, transparent 70%)`,
75
104
  ).join(', ')
76
105
  let root: HTMLDivElement | undefined
77
106
 
@@ -93,11 +122,11 @@ export function Aurora(props: AuroraProps): JSX.Element {
93
122
  when={props.variant === 'mesh'}
94
123
  fallback={
95
124
  <For each={BLOBS}>
96
- {(b) => (
125
+ {(b, i) => (
97
126
  <div
98
127
  class={cn('absolute rounded-full blur-3xl', b.pos, b.size, props.animated && 'aurora-drift')}
99
128
  style={{
100
- background: `radial-gradient(circle, hsl(var(${b.token}) / ${(intensity() * b.a).toFixed(2)}), transparent 70%)`,
129
+ background: `radial-gradient(circle, ${colorAt(colorFor(i(), b.token), intensity() * b.a)}, transparent 70%)`,
101
130
  }}
102
131
  />
103
132
  )}
@@ -110,12 +139,12 @@ export function Aurora(props: AuroraProps): JSX.Element {
110
139
  />
111
140
  </Show>
112
141
  {/* Soft glow that follows the cursor across the backdrop (positioned by
113
- bindPointerFx via left/top on pointermove). */}
142
+ bindPointerFx via transform, coalesced per frame). */}
114
143
  <div
115
144
  id="cursorGlow"
116
- class="absolute h-[520px] w-[520px] rounded-full opacity-0 transition-opacity duration-300"
145
+ class="absolute h-[520px] w-[520px] rounded-full opacity-0 transition-opacity duration-300 will-change-transform"
117
146
  style={{
118
- background: 'radial-gradient(circle, hsl(var(--primary) / 0.1), transparent 70%)',
147
+ background: `radial-gradient(circle, ${colorAt(props.glowColor ?? '--primary', props.glowOpacity ?? 0.1)}, transparent 70%)`,
119
148
  transform: 'translate(-50%,-50%)',
120
149
  }}
121
150
  />
@@ -8,11 +8,29 @@
8
8
  export function bindPointerFx(root: HTMLElement): () => void {
9
9
  const glow = root.querySelector<HTMLElement>('#cursorGlow')
10
10
 
11
+ // The glow is moved with `transform` only (compositor-friendly: no layout, and
12
+ // no repaint of backdrop-filter surfaces in front of it). It is pinned at 0,0
13
+ // once; pointermoves are coalesced to one write per animation frame.
14
+ let frame = 0
15
+ let gx = 0
16
+ let gy = 0
17
+ if (glow) {
18
+ glow.style.left = '0px'
19
+ glow.style.top = '0px'
20
+ glow.style.willChange = 'transform'
21
+ }
22
+ const paintGlow = (): void => {
23
+ frame = 0
24
+ if (!glow) return
25
+ glow.style.transform = `translate3d(${gx}px, ${gy}px, 0) translate(-50%, -50%)`
26
+ }
27
+
11
28
  const onMove = (e: PointerEvent): void => {
12
29
  if (glow) {
13
- glow.style.opacity = '1'
14
- glow.style.left = `${e.clientX}px`
15
- glow.style.top = `${e.clientY}px`
30
+ gx = e.clientX
31
+ gy = e.clientY
32
+ if (glow.style.opacity !== '1') glow.style.opacity = '1'
33
+ if (!frame) frame = requestAnimationFrame(paintGlow)
16
34
  }
17
35
  const el = e.target as Element | null
18
36
  const magnetic = el?.closest?.('.magnetic') as HTMLElement | null
@@ -42,6 +60,7 @@ export function bindPointerFx(root: HTMLElement): () => void {
42
60
  document.addEventListener('pointerout', onOut as EventListener)
43
61
 
44
62
  return () => {
63
+ if (frame) cancelAnimationFrame(frame)
45
64
  document.removeEventListener('pointermove', onMove as EventListener)
46
65
  window.removeEventListener('pointerleave', onLeave)
47
66
  document.removeEventListener('pointerout', onOut as EventListener)
@@ -1,6 +1,9 @@
1
1
  // Confirmation dialog on Kobalte's AlertDialog primitive — centered modal,
2
2
  // reuses the modal-overlay/modal-content keyframes from styles.css.
3
- import { AlertDialog as KAlertDialog } from '@kobalte/core/alert-dialog'
3
+ // Namespace import on purpose: Kobalte's `AlertDialog` is built with Object.assign on the
4
+ // same `DialogRoot` function as `Dialog`, so `AlertDialog.Content` depends on module load order (#118).
5
+ // The named exports (`Root`, `Content`, ...) are not affected.
6
+ import * as KAlertDialog from '@kobalte/core/alert-dialog'
4
7
  import type { JSX, ParentProps } from 'solid-js'
5
8
  import { Show } from 'solid-js'
6
9
 
@@ -30,7 +33,7 @@ interface AlertDialogProps extends ParentProps {
30
33
  */
31
34
  export function AlertDialog(props: AlertDialogProps): JSX.Element {
32
35
  return (
33
- <KAlertDialog open={props.open} onOpenChange={props.onOpenChange}>
36
+ <KAlertDialog.Root open={props.open} onOpenChange={props.onOpenChange}>
34
37
  <KAlertDialog.Portal>
35
38
  <KAlertDialog.Overlay class="modal-overlay fixed inset-0 z-40 bg-background/80 backdrop-blur-sm" />
36
39
  <div class="fixed inset-0 z-50 flex items-center justify-center p-4">
@@ -53,6 +56,6 @@ export function AlertDialog(props: AlertDialogProps): JSX.Element {
53
56
  </KAlertDialog.Content>
54
57
  </div>
55
58
  </KAlertDialog.Portal>
56
- </KAlertDialog>
59
+ </KAlertDialog.Root>
57
60
  )
58
61
  }
@@ -0,0 +1,35 @@
1
+ import { splitProps, type JSX, type ParentProps } from 'solid-js'
2
+
3
+ import { cn } from '../lib/cn'
4
+
5
+ interface AspectRatioProps extends ParentProps {
6
+ /** Width divided by height, e.g. `16 / 9`. @default 1 */
7
+ ratio?: number
8
+ class?: string
9
+ }
10
+
11
+ export type { AspectRatioProps }
12
+
13
+ /**
14
+ * Box that keeps a fixed width:height ratio (CSS `aspect-ratio`). Children fill
15
+ * it; give images/video `object-cover`.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * <AspectRatio ratio={16 / 9} class="w-full overflow-hidden rounded-lg">
20
+ * <img src="/cover.jpg" alt="" class="object-cover" />
21
+ * </AspectRatio>
22
+ * ```
23
+ */
24
+ export function AspectRatio(props: AspectRatioProps): JSX.Element {
25
+ const [local, rest] = splitProps(props, ['ratio', 'class', 'children'])
26
+ return (
27
+ <div
28
+ class={cn('relative w-full [&>*]:h-full [&>*]:w-full', local.class)}
29
+ style={{ 'aspect-ratio': String(local.ratio ?? 1) }}
30
+ {...rest}
31
+ >
32
+ {local.children}
33
+ </div>
34
+ )
35
+ }
@@ -0,0 +1,189 @@
1
+ // Attachment — a file/image chip for chat UIs, built on the `Item` row primitive.
2
+ // Adds an upload lifecycle (idle / uploading / processing / error / done), a
3
+ // progress bar and a horizontally scrolling group.
4
+ import { splitProps, type JSX, type ParentProps, Show } from 'solid-js'
5
+
6
+ import { cn } from '../lib/cn'
7
+ import { Item, ItemActions, ItemContent, ItemDescription, ItemMedia, ItemTitle, type ItemSize } from './Item'
8
+ import { ScrollArea } from './ScrollArea'
9
+
10
+ export type AttachmentState = 'idle' | 'uploading' | 'processing' | 'error' | 'done'
11
+ export type AttachmentOrientation = 'horizontal' | 'vertical'
12
+
13
+ interface AttachmentProps extends ParentProps {
14
+ /** Lifecycle state. `uploading`/`processing` set `aria-busy`; `error` uses destructive tokens. @default 'idle' */
15
+ state?: AttachmentState
16
+ /** Density, forwarded to `Item`. @default 'default' */
17
+ size?: ItemSize
18
+ /** Lay media and text side by side, or stacked. @default 'horizontal' */
19
+ orientation?: AttachmentOrientation
20
+ /**
21
+ * Upload progress, 0–100. Only used while `state="uploading"`; when omitted
22
+ * the bar is indeterminate (no `aria-valuenow`).
23
+ */
24
+ progress?: number
25
+ /** Accessible name of the progress bar. @default 'Uploading' / 'Processing' */
26
+ progressLabel?: string
27
+ class?: string
28
+ }
29
+
30
+ export type { AttachmentProps }
31
+
32
+ const clamp = (n: number): number => Math.min(100, Math.max(0, n))
33
+
34
+ /**
35
+ * A file or image attachment: `AttachmentMedia`, an `AttachmentContent` with
36
+ * title/description and trailing `AttachmentActions`. Group several in an
37
+ * `AttachmentGroup` (horizontal scroll-snap row).
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * <Attachment state="uploading" progress={42}>
42
+ * <AttachmentMedia><FileText /></AttachmentMedia>
43
+ * <AttachmentContent>
44
+ * <AttachmentTitle>roadmap.pdf</AttachmentTitle>
45
+ * <AttachmentDescription>42% of 2.4 MB</AttachmentDescription>
46
+ * </AttachmentContent>
47
+ * <AttachmentActions>
48
+ * <AttachmentAction label="Cancel upload"><X /></AttachmentAction>
49
+ * </AttachmentActions>
50
+ * </Attachment>
51
+ * ```
52
+ */
53
+ export function Attachment(props: AttachmentProps): JSX.Element {
54
+ const [local] = splitProps(props, [
55
+ 'state',
56
+ 'size',
57
+ 'orientation',
58
+ 'progress',
59
+ 'progressLabel',
60
+ 'class',
61
+ 'children',
62
+ ])
63
+ const state = (): AttachmentState => local.state ?? 'idle'
64
+ const busy = (): boolean => state() === 'uploading' || state() === 'processing'
65
+ const determinate = (): boolean => state() === 'uploading' && local.progress !== undefined
66
+ return (
67
+ <div
68
+ data-slot="attachment"
69
+ data-state={state()}
70
+ data-orientation={local.orientation ?? 'horizontal'}
71
+ aria-busy={busy() ? 'true' : undefined}
72
+ class={cn(
73
+ 'relative w-full overflow-hidden rounded-lg border bg-card/60 text-card-foreground',
74
+ state() === 'error'
75
+ ? 'border-destructive/50 bg-destructive/10 text-destructive [&_[data-slot=item-description]]:text-destructive/80'
76
+ : 'border-border',
77
+ local.class,
78
+ )}
79
+ >
80
+ <Item
81
+ size={local.size}
82
+ class={cn(
83
+ 'flex-nowrap border-0',
84
+ local.orientation === 'vertical' && 'flex-col items-start',
85
+ state() === 'processing' && 'animate-pulse',
86
+ )}
87
+ >
88
+ {local.children}
89
+ </Item>
90
+ <Show when={busy()}>
91
+ <div
92
+ role="progressbar"
93
+ aria-label={local.progressLabel ?? (state() === 'uploading' ? 'Uploading' : 'Processing')}
94
+ aria-valuemin={0}
95
+ aria-valuemax={100}
96
+ aria-valuenow={determinate() ? Math.round(clamp(local.progress ?? 0)) : undefined}
97
+ class="absolute inset-x-0 bottom-0 h-1 bg-muted"
98
+ >
99
+ <div
100
+ class={cn(
101
+ 'h-full bg-primary transition-[width] duration-200',
102
+ !determinate() && 'w-1/3 animate-pulse',
103
+ )}
104
+ style={determinate() ? { width: `${clamp(local.progress ?? 0)}%` } : undefined}
105
+ />
106
+ </div>
107
+ </Show>
108
+ </div>
109
+ )
110
+ }
111
+
112
+ interface SlotProps extends ParentProps {
113
+ class?: string
114
+ }
115
+
116
+ /** Leading visual: an icon tile (`icon`, default) or an image thumbnail (`image`). */
117
+ export function AttachmentMedia(props: SlotProps & { variant?: 'icon' | 'image' }): JSX.Element {
118
+ return (
119
+ <ItemMedia variant={props.variant ?? 'icon'} class={props.class}>
120
+ {props.children}
121
+ </ItemMedia>
122
+ )
123
+ }
124
+
125
+ /** The text column. */
126
+ export function AttachmentContent(props: SlotProps): JSX.Element {
127
+ return <ItemContent class={props.class}>{props.children}</ItemContent>
128
+ }
129
+
130
+ /** File name; truncates on one line. */
131
+ export function AttachmentTitle(props: SlotProps): JSX.Element {
132
+ return <ItemTitle class={cn('block truncate', props.class)}>{props.children}</ItemTitle>
133
+ }
134
+
135
+ /** Secondary line: size, status or error message. */
136
+ export function AttachmentDescription(props: SlotProps): JSX.Element {
137
+ return <ItemDescription class={cn('line-clamp-1 text-xs', props.class)}>{props.children}</ItemDescription>
138
+ }
139
+
140
+ /** Trailing controls. */
141
+ export function AttachmentActions(props: SlotProps): JSX.Element {
142
+ return <ItemActions class={cn('gap-1', props.class)}>{props.children}</ItemActions>
143
+ }
144
+
145
+ interface AttachmentActionProps extends ParentProps {
146
+ /** Accessible name (required: the button only holds an icon). */
147
+ label: string
148
+ onClick?: (event: MouseEvent) => void
149
+ disabled?: boolean
150
+ class?: string
151
+ }
152
+
153
+ export type { AttachmentActionProps }
154
+
155
+ /** Icon button inside `AttachmentActions`. */
156
+ export function AttachmentAction(props: AttachmentActionProps): JSX.Element {
157
+ return (
158
+ <button
159
+ type="button"
160
+ aria-label={props.label}
161
+ disabled={props.disabled}
162
+ onClick={(e) => props.onClick?.(e)}
163
+ class={cn(
164
+ 'grid h-7 w-7 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&>svg]:h-4 [&>svg]:w-4',
165
+ props.class,
166
+ )}
167
+ >
168
+ {props.children}
169
+ </button>
170
+ )
171
+ }
172
+
173
+ /** Horizontal scroll-snap row of attachments. */
174
+ export function AttachmentGroup(props: SlotProps & { label?: string }): JSX.Element {
175
+ return (
176
+ <ScrollArea
177
+ orientation="horizontal"
178
+ label={props.label ?? 'Attachments'}
179
+ class={cn('w-full snap-x snap-mandatory rounded-lg', props.class)}
180
+ >
181
+ <div
182
+ data-slot="attachment-group"
183
+ class="flex w-max gap-2 p-1 [&>[data-slot=attachment]]:w-64 [&>[data-slot=attachment]]:shrink-0 [&>[data-slot=attachment]]:snap-start"
184
+ >
185
+ {props.children}
186
+ </div>
187
+ </ScrollArea>
188
+ )
189
+ }