@a4ui/core 0.11.0 → 0.12.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 (127) hide show
  1. package/README.md +36 -26
  2. package/dist/full.css +3042 -0
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +802 -784
  5. package/dist/lib/motion.d.ts +19 -1
  6. package/package.json +8 -1
  7. package/src/charts/BarChart.tsx +55 -0
  8. package/src/charts/DonutChart.tsx +127 -0
  9. package/src/charts/Sparkline.tsx +101 -0
  10. package/src/charts/index.ts +5 -0
  11. package/src/commerce/CartLine.tsx +84 -0
  12. package/src/commerce/CartSummary.tsx +70 -0
  13. package/src/commerce/FilterGroup.tsx +70 -0
  14. package/src/commerce/PriceTag.tsx +62 -0
  15. package/src/commerce/ProductCard.tsx +121 -0
  16. package/src/commerce/ProductGrid.tsx +27 -0
  17. package/src/commerce/QuantityStepper.tsx +59 -0
  18. package/src/commerce/index.ts +10 -0
  19. package/src/elements.tsx +167 -0
  20. package/src/index.ts +166 -0
  21. package/src/layout/AppShell.tsx +102 -0
  22. package/src/layout/ChristmasBackground.tsx +177 -0
  23. package/src/layout/EffectsToggle.tsx +41 -0
  24. package/src/layout/NavGroup.tsx +45 -0
  25. package/src/layout/SnowScenery.tsx +76 -0
  26. package/src/layout/SpaceBackground.tsx +459 -0
  27. package/src/layout/ThemeToggle.tsx +40 -0
  28. package/src/layout/ThemedScenery.tsx +179 -0
  29. package/src/layout/sceneEffects.ts +49 -0
  30. package/src/lib/cn.ts +17 -0
  31. package/src/lib/effects.ts +71 -0
  32. package/src/lib/media.ts +27 -0
  33. package/src/lib/motion.ts +191 -0
  34. package/src/lib/theme.ts +113 -0
  35. package/src/lib/virtual.ts +33 -0
  36. package/src/styles/space.css +510 -0
  37. package/src/styles/tokens.css +213 -0
  38. package/src/themes/index.ts +102 -0
  39. package/src/themes/palettes.ts +368 -0
  40. package/src/ui/Accordion.tsx +65 -0
  41. package/src/ui/Affix.tsx +72 -0
  42. package/src/ui/Alert.tsx +55 -0
  43. package/src/ui/AlertDialog.tsx +58 -0
  44. package/src/ui/Anchor.tsx +100 -0
  45. package/src/ui/Avatar.tsx +34 -0
  46. package/src/ui/AvatarGroup.tsx +55 -0
  47. package/src/ui/BackToTop.tsx +51 -0
  48. package/src/ui/Badge.tsx +46 -0
  49. package/src/ui/BottomNavigation.tsx +65 -0
  50. package/src/ui/Breadcrumb.tsx +64 -0
  51. package/src/ui/Button.tsx +49 -0
  52. package/src/ui/Calendar.tsx +40 -0
  53. package/src/ui/CalendarHeatmap.tsx +180 -0
  54. package/src/ui/Card.tsx +84 -0
  55. package/src/ui/Carousel.tsx +121 -0
  56. package/src/ui/Cascader.tsx +158 -0
  57. package/src/ui/Checkbox.tsx +35 -0
  58. package/src/ui/Clock.tsx +221 -0
  59. package/src/ui/Collapse.tsx +48 -0
  60. package/src/ui/ColorPicker.tsx +89 -0
  61. package/src/ui/Combobox.tsx +67 -0
  62. package/src/ui/Command.tsx +158 -0
  63. package/src/ui/Comment.tsx +91 -0
  64. package/src/ui/ContextMenu.tsx +58 -0
  65. package/src/ui/Countdown.tsx +123 -0
  66. package/src/ui/DataGrid.tsx +177 -0
  67. package/src/ui/DateField.tsx +177 -0
  68. package/src/ui/DateRangePicker.tsx +204 -0
  69. package/src/ui/DateTimeField.tsx +68 -0
  70. package/src/ui/Descriptions.tsx +56 -0
  71. package/src/ui/Drawer.tsx +72 -0
  72. package/src/ui/Dropdown.tsx +71 -0
  73. package/src/ui/Dropzone.tsx +88 -0
  74. package/src/ui/Empty.tsx +53 -0
  75. package/src/ui/FileUpload.tsx +198 -0
  76. package/src/ui/FloatingActionButton.tsx +48 -0
  77. package/src/ui/Form.tsx +108 -0
  78. package/src/ui/Highlight.tsx +53 -0
  79. package/src/ui/HoverCard.tsx +42 -0
  80. package/src/ui/Image.tsx +66 -0
  81. package/src/ui/Input.tsx +38 -0
  82. package/src/ui/Kbd.tsx +25 -0
  83. package/src/ui/List.tsx +71 -0
  84. package/src/ui/Marquee.tsx +48 -0
  85. package/src/ui/Mentions.tsx +170 -0
  86. package/src/ui/Meter.tsx +57 -0
  87. package/src/ui/Modal.tsx +106 -0
  88. package/src/ui/MultiSelect.tsx +236 -0
  89. package/src/ui/NotificationCenter.tsx +103 -0
  90. package/src/ui/NumberInput.tsx +48 -0
  91. package/src/ui/PageHeader.tsx +60 -0
  92. package/src/ui/Pagination.tsx +62 -0
  93. package/src/ui/Popover.tsx +42 -0
  94. package/src/ui/Portal.tsx +34 -0
  95. package/src/ui/Progress.tsx +55 -0
  96. package/src/ui/RadioGroup.tsx +65 -0
  97. package/src/ui/Rating.tsx +98 -0
  98. package/src/ui/Result.tsx +75 -0
  99. package/src/ui/RingProgress.tsx +75 -0
  100. package/src/ui/SegmentedControl.tsx +61 -0
  101. package/src/ui/Select.tsx +55 -0
  102. package/src/ui/Separator.tsx +39 -0
  103. package/src/ui/Skeleton.tsx +23 -0
  104. package/src/ui/Slider.tsx +57 -0
  105. package/src/ui/Sortable.tsx +174 -0
  106. package/src/ui/SpeedDial.tsx +75 -0
  107. package/src/ui/Spinner.tsx +33 -0
  108. package/src/ui/Splitter.tsx +115 -0
  109. package/src/ui/Stat.tsx +74 -0
  110. package/src/ui/Stepper.tsx +134 -0
  111. package/src/ui/Switch.tsx +44 -0
  112. package/src/ui/Table.tsx +128 -0
  113. package/src/ui/Tabs.tsx +72 -0
  114. package/src/ui/TagInput.tsx +85 -0
  115. package/src/ui/Textarea.tsx +38 -0
  116. package/src/ui/TimeField.tsx +298 -0
  117. package/src/ui/Timeline.tsx +89 -0
  118. package/src/ui/Toast.tsx +68 -0
  119. package/src/ui/Toggle.tsx +41 -0
  120. package/src/ui/ToggleGroup.tsx +59 -0
  121. package/src/ui/Tooltip.tsx +42 -0
  122. package/src/ui/Tour.tsx +197 -0
  123. package/src/ui/Transfer.tsx +112 -0
  124. package/src/ui/Tree.tsx +100 -0
  125. package/src/ui/TreeSelect.tsx +151 -0
  126. package/src/ui/VirtualList.tsx +81 -0
  127. package/src/ui/internal/CalendarCore.tsx +276 -0
@@ -0,0 +1,177 @@
1
+ // Christmas scenery for the "christmas" theme: a cozy night sky, drifting snow, a
2
+ // twinkling light garland across the top, a pine tree, and — instead of a
3
+ // shooting star — Santa's sleigh crossing the sky (ambiently + on a background
4
+ // click). Shares the cursor-glow / magnetic / edge-glow pointer effects. Motion
5
+ // is skipped under motionReduced().
6
+ import { createEffect, For, onCleanup, onMount, type JSX } from 'solid-js'
7
+
8
+ import { motionReduced } from '../lib/motion'
9
+ import { bindPointerFx } from './sceneEffects'
10
+
11
+ const FLAKE_COUNT = 60
12
+ const LIGHT_COLORS = ['#ff5a5a', '#4ade80', '#ffd23f', '#5aa9ff', '#ff8ad8']
13
+ const GARLAND = Array.from({ length: 22 }, (_, i) => i)
14
+
15
+ function buildFlakes(animate: boolean): string {
16
+ const rnd = Math.random
17
+ let html = ''
18
+ for (let i = 0; i < FLAKE_COUNT; i++) {
19
+ const size = (2 + rnd() * 4).toFixed(1)
20
+ const op = (0.4 + rnd() * 0.6).toFixed(2)
21
+ const left = (rnd() * 100).toFixed(2)
22
+ const dur = (6 + rnd() * 9).toFixed(1)
23
+ const delay = (-rnd() * 15).toFixed(1)
24
+ const sway = (rnd() * 2 - 1) * (8 + rnd() * 12)
25
+ const anim = animate
26
+ ? `animation:snowFall ${dur}s linear ${delay}s infinite;`
27
+ : `top:${(rnd() * 100).toFixed(1)}%;`
28
+ html +=
29
+ `<span class="flake" style="left:${left}%;width:${size}px;height:${size}px;opacity:${op};` +
30
+ `--sway:${sway.toFixed(0)}px;${anim}"></span>`
31
+ }
32
+ return html
33
+ }
34
+
35
+ /**
36
+ * Festive backdrop for the `christmas` theme: night sky, falling snow, a
37
+ * twinkling light garland, a pine tree, and Santa's sleigh flying across (ambient
38
+ * + on background click). Mount as {@link AppShell}'s `background`. Motion is
39
+ * skipped under `motionReduced()`.
40
+ */
41
+ export function ChristmasBackground(): JSX.Element {
42
+ let root!: HTMLDivElement
43
+ let field!: HTMLDivElement
44
+
45
+ createEffect(() => {
46
+ field.innerHTML = buildFlakes(!motionReduced())
47
+ })
48
+
49
+ onMount(() => {
50
+ const cleanups: Array<() => void> = []
51
+ const timers: number[] = []
52
+
53
+ // Santa's sleigh crosses from one side to the other with a gentle arc.
54
+ const launchSanta = (rtl: boolean): void => {
55
+ const el = document.createElement('span')
56
+ el.textContent = '🎅🛷'
57
+ const top = 6 + Math.random() * 26
58
+ const size = 30 + Math.random() * 16
59
+ el.style.cssText =
60
+ `position:absolute;top:${top}vh;${rtl ? 'right' : 'left'}:-14%;font-size:${size.toFixed(0)}px;` +
61
+ `white-space:nowrap;pointer-events:none;filter:drop-shadow(0 0 6px hsl(var(--primary) / 0.5));`
62
+ if (rtl) el.style.transform = 'scaleX(-1)'
63
+ root.appendChild(el)
64
+ const dist = window.innerWidth * 1.3
65
+ const ex = rtl ? -dist : dist
66
+ const dur = 6500 + Math.random() * 4000
67
+ const anim = el.animate(
68
+ [
69
+ { opacity: 0, transform: `translate(0,0) ${rtl ? 'scaleX(-1)' : ''}` },
70
+ {
71
+ opacity: 1,
72
+ transform: `translate(${ex * 0.15}px,-18px) ${rtl ? 'scaleX(-1)' : ''}`,
73
+ offset: 0.15,
74
+ },
75
+ {
76
+ opacity: 1,
77
+ transform: `translate(${ex * 0.85}px,-18px) ${rtl ? 'scaleX(-1)' : ''}`,
78
+ offset: 0.85,
79
+ },
80
+ { opacity: 0, transform: `translate(${ex}px,0) ${rtl ? 'scaleX(-1)' : ''}` },
81
+ ],
82
+ { duration: dur, easing: 'cubic-bezier(.4,0,.6,1)', fill: 'forwards' },
83
+ )
84
+ anim.onfinish = () => el.remove()
85
+ timers.push(window.setTimeout(() => el.remove(), dur + 400))
86
+ }
87
+
88
+ const INTERACTIVE =
89
+ 'a, button, input, select, textarea, label, [role="button"], [role="tab"], [role="menuitem"], [role="dialog"], [contenteditable]'
90
+ const onClick = (e: MouseEvent): void => {
91
+ if ((e.target as Element | null)?.closest?.(INTERACTIVE)) return
92
+ launchSanta(Math.random() < 0.5)
93
+ }
94
+ document.addEventListener('click', onClick)
95
+ cleanups.push(() => document.removeEventListener('click', onClick))
96
+
97
+ if (!motionReduced()) {
98
+ cleanups.push(bindPointerFx(root))
99
+ const loop = (): void => {
100
+ launchSanta(Math.random() < 0.5)
101
+ timers.push(window.setTimeout(loop, 9000 + Math.random() * 9000))
102
+ }
103
+ timers.push(window.setTimeout(loop, 3000))
104
+ }
105
+
106
+ onCleanup(() => {
107
+ cleanups.forEach((fn) => fn())
108
+ timers.forEach((t) => clearTimeout(t))
109
+ })
110
+ })
111
+
112
+ return (
113
+ <div id="xmas" ref={root} aria-hidden="true">
114
+ <div class="xmas-sky" />
115
+ <div ref={field} class="absolute inset-0" />
116
+
117
+ {/* Light garland across the top */}
118
+ <div style={{ position: 'absolute', top: '0', left: '0', right: '0', height: '26px' }}>
119
+ <For each={GARLAND}>
120
+ {(i) => {
121
+ const c = LIGHT_COLORS[i % LIGHT_COLORS.length]
122
+ return (
123
+ <span
124
+ class="light"
125
+ style={{
126
+ left: `${((i / (GARLAND.length - 1)) * 100).toFixed(2)}%`,
127
+ background: c,
128
+ color: c,
129
+ 'animation-delay': `${(i % 5) * 0.3}s`,
130
+ }}
131
+ />
132
+ )
133
+ }}
134
+ </For>
135
+ </div>
136
+
137
+ {/* Pine tree (accent = pine green, so it follows the theme) */}
138
+ <svg
139
+ viewBox="0 0 200 270"
140
+ style={{
141
+ position: 'absolute',
142
+ bottom: '0',
143
+ left: '50%',
144
+ transform: 'translateX(-50%)',
145
+ width: '240px',
146
+ height: 'auto',
147
+ opacity: '0.9',
148
+ }}
149
+ >
150
+ <rect x="92" y="228" width="16" height="42" fill="hsl(25 45% 28%)" />
151
+ <polygon points="100,64 60,150 140,150" fill="hsl(var(--accent))" />
152
+ <polygon points="100,110 48,196 152,196" fill="hsl(var(--accent))" />
153
+ <polygon points="100,158 38,240 162,240" fill="hsl(var(--accent))" />
154
+ <polygon points="100,40 106,58 125,58 110,70 116,88 100,77 84,88 90,70 75,58 94,58" fill="#ffd23f" />
155
+ <circle cx="82" cy="180" r="5" fill="#ff5a5a" />
156
+ <circle cx="120" cy="205" r="5" fill="#5aa9ff" />
157
+ <circle cx="100" cy="150" r="5" fill="#ffd23f" />
158
+ <circle cx="72" cy="225" r="5" fill="#ff8ad8" />
159
+ <circle cx="132" cy="228" r="5" fill="#ffd23f" />
160
+ </svg>
161
+
162
+ <div
163
+ id="cursorGlow"
164
+ style={{
165
+ position: 'absolute',
166
+ width: '520px',
167
+ height: '520px',
168
+ 'border-radius': '50%',
169
+ background: 'radial-gradient(circle, hsl(var(--primary) / 0.1), transparent 70%)',
170
+ transform: 'translate(-50%,-50%)',
171
+ opacity: '0',
172
+ transition: 'opacity .4s ease',
173
+ }}
174
+ />
175
+ </div>
176
+ )
177
+ }
@@ -0,0 +1,41 @@
1
+ // Single "visual effects" toggle. Pressed (default) = full space-glass
2
+ // experience: translucent glass, starfield, animations. Un-press it for calm
3
+ // mode (opaque surfaces, no starfield, no motion) — friendlier for contrast,
4
+ // motion sensitivity and low-power devices.
5
+ import { Sparkles } from 'lucide-solid'
6
+
7
+ import { setEffects, useEffects } from '../lib/effects'
8
+
9
+ /** Props for {@link EffectsToggle}. */
10
+ interface EffectsToggleProps {
11
+ /** Accessible label / tooltip text. Defaults to `'Visual effects'`. */
12
+ label?: string
13
+ }
14
+
15
+ /**
16
+ * Icon button that toggles the "visual effects" switch: pressed (default) is
17
+ * the full space-glass experience (translucent glass, starfield, animation);
18
+ * un-pressed is calm mode (opaque surfaces, no starfield, no motion) — useful
19
+ * for contrast, motion sensitivity, and low-power devices. Wraps
20
+ * {@link useEffects} + {@link setEffects}.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * <EffectsToggle label="Toggle effects" />
25
+ * ```
26
+ */
27
+ export function EffectsToggle(props: EffectsToggleProps) {
28
+ const on = useEffects()
29
+ return (
30
+ <button
31
+ type="button"
32
+ class="grid h-9 w-9 place-items-center rounded-lg text-muted-foreground transition hover:bg-muted hover:text-foreground aria-pressed:text-primary"
33
+ aria-pressed={on()}
34
+ aria-label={props.label ?? 'Visual effects'}
35
+ title={on() ? 'Visual effects on — click for calm mode' : 'Calm mode — click to enable effects'}
36
+ onClick={() => setEffects(!on())}
37
+ >
38
+ <Sparkles class="h-[18px] w-[18px]" />
39
+ </button>
40
+ )
41
+ }
@@ -0,0 +1,45 @@
1
+ // Collapsible sidebar category — native <details>/<summary>, no JS state, chevron
2
+ // rotates via the `group-[[open]]:rotate-90` arbitrary-variant selector. Open by
3
+ // default (every group starts expanded).
4
+ import type { JSX, ParentProps } from 'solid-js'
5
+
6
+ /** Props for {@link NavGroup}. */
7
+ interface NavGroupProps extends ParentProps {
8
+ /** Category heading shown in the summary row (rendered uppercase). */
9
+ title: string
10
+ }
11
+
12
+ /**
13
+ * Collapsible sidebar nav category. Uses native `<details>`/`<summary>` (no
14
+ * JS state), with a chevron that rotates via a `group-[[open]]` CSS variant.
15
+ * Expanded by default; wrap a list of nav links as `children`.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * <NavGroup title="Settings">
20
+ * <NavLink href="/settings/profile">Profile</NavLink>
21
+ * <NavLink href="/settings/billing">Billing</NavLink>
22
+ * </NavGroup>
23
+ * ```
24
+ */
25
+ export function NavGroup(props: NavGroupProps): JSX.Element {
26
+ return (
27
+ <details open class="group">
28
+ <summary class="mt-3 flex cursor-pointer list-none select-none items-center justify-between rounded-md px-3 py-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground transition-colors hover:bg-muted hover:text-foreground [&::-webkit-details-marker]:hidden">
29
+ <span>{props.title}</span>
30
+ <svg
31
+ class="h-3.5 w-3.5 shrink-0 transition-transform duration-200 group-[[open]]:rotate-90"
32
+ viewBox="0 0 20 20"
33
+ fill="none"
34
+ stroke="currentColor"
35
+ stroke-width="2"
36
+ stroke-linecap="round"
37
+ stroke-linejoin="round"
38
+ >
39
+ <path d="M7 5l6 5-6 5" />
40
+ </svg>
41
+ </summary>
42
+ <div class="ml-4 mt-1 space-y-0.5 border-l border-border pl-2">{props.children}</div>
43
+ </details>
44
+ )
45
+ }
@@ -0,0 +1,76 @@
1
+ // Snow scenery for the "snow" theme: snowflakes drifting DOWN with a sideways
2
+ // sway, a snow bank building at the bottom, and a frosted cap on cards (via the
3
+ // `data-scene="snow"` flag on <html>). Shares the cursor glow / magnetic / edge-
4
+ // glow pointer effects. Motion is skipped under motionReduced().
5
+ import { createEffect, onCleanup, onMount, type JSX } from 'solid-js'
6
+
7
+ import { motionReduced } from '../lib/motion'
8
+ import { bindPointerFx } from './sceneEffects'
9
+
10
+ const FLAKE_COUNT = 80
11
+
12
+ function buildFlakes(animate: boolean): string {
13
+ const rnd = Math.random
14
+ let html = ''
15
+ for (let i = 0; i < FLAKE_COUNT; i++) {
16
+ const size = (2 + rnd() * 5).toFixed(1)
17
+ const op = (0.4 + rnd() * 0.6).toFixed(2)
18
+ const left = (rnd() * 100).toFixed(2)
19
+ const dur = (5 + rnd() * 9).toFixed(1)
20
+ const delay = (-rnd() * 14).toFixed(1)
21
+ const sway = (rnd() * 2 - 1) * (8 + rnd() * 14)
22
+ const blur = rnd() < 0.3 ? 'filter:blur(1px);' : ''
23
+ const anim = animate
24
+ ? `animation:snowFall ${dur}s linear ${delay}s infinite;`
25
+ : `top:${(rnd() * 100).toFixed(1)}%;`
26
+ html +=
27
+ `<span class="flake" style="left:${left}%;width:${size}px;height:${size}px;opacity:${op};` +
28
+ `--sway:${sway.toFixed(0)}px;${blur}${anim}"></span>`
29
+ }
30
+ return html
31
+ }
32
+
33
+ /**
34
+ * Falling-snow backdrop for the `snow` theme: snowflakes drift down, a snow bank
35
+ * gathers at the bottom, and cards get a frosted cap. Mount as {@link AppShell}'s
36
+ * `background`. Motion is skipped under `motionReduced()`.
37
+ */
38
+ export function SnowScenery(): JSX.Element {
39
+ let root!: HTMLDivElement
40
+ let field!: HTMLDivElement
41
+
42
+ createEffect(() => {
43
+ field.innerHTML = buildFlakes(!motionReduced())
44
+ })
45
+
46
+ onMount(() => {
47
+ document.documentElement.dataset.scene = 'snow'
48
+ const cleanups: Array<() => void> = []
49
+ if (!motionReduced()) cleanups.push(bindPointerFx(root))
50
+ onCleanup(() => {
51
+ if (document.documentElement.dataset.scene === 'snow') delete document.documentElement.dataset.scene
52
+ cleanups.forEach((fn) => fn())
53
+ })
54
+ })
55
+
56
+ return (
57
+ <div id="snow" ref={root} aria-hidden="true">
58
+ <div class="snow-sky" />
59
+ <div ref={field} class="absolute inset-0" />
60
+ <div class="snow-bank" />
61
+ <div
62
+ id="cursorGlow"
63
+ style={{
64
+ position: 'absolute',
65
+ width: '520px',
66
+ height: '520px',
67
+ 'border-radius': '50%',
68
+ background: 'radial-gradient(circle, hsl(var(--primary) / 0.1), transparent 70%)',
69
+ transform: 'translate(-50%,-50%)',
70
+ opacity: '0',
71
+ transition: 'opacity .4s ease',
72
+ }}
73
+ />
74
+ </div>
75
+ )
76
+ }