@khal-os/ui 1.0.0 → 1.0.2

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 (59) hide show
  1. package/LICENSE +94 -0
  2. package/README.md +25 -0
  3. package/dist/index.cjs +2661 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +926 -0
  6. package/dist/index.d.ts +926 -0
  7. package/dist/index.js +2510 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +59 -40
  10. package/tokens.css +260 -238
  11. package/src/components/ContextMenu.tsx +0 -130
  12. package/src/components/avatar.tsx +0 -71
  13. package/src/components/badge.tsx +0 -39
  14. package/src/components/button.tsx +0 -102
  15. package/src/components/command.tsx +0 -165
  16. package/src/components/cost-counter.tsx +0 -75
  17. package/src/components/data-row.tsx +0 -97
  18. package/src/components/dropdown-menu.tsx +0 -233
  19. package/src/components/glass-card.tsx +0 -74
  20. package/src/components/input.tsx +0 -48
  21. package/src/components/khal-logo.tsx +0 -73
  22. package/src/components/live-feed.tsx +0 -109
  23. package/src/components/mesh-gradient.tsx +0 -57
  24. package/src/components/metric-display.tsx +0 -93
  25. package/src/components/note.tsx +0 -55
  26. package/src/components/number-flow.tsx +0 -25
  27. package/src/components/pill-badge.tsx +0 -65
  28. package/src/components/progress-bar.tsx +0 -70
  29. package/src/components/section-card.tsx +0 -76
  30. package/src/components/separator.tsx +0 -25
  31. package/src/components/spinner.tsx +0 -42
  32. package/src/components/status-dot.tsx +0 -90
  33. package/src/components/switch.tsx +0 -36
  34. package/src/components/theme-provider.tsx +0 -58
  35. package/src/components/theme-switcher.tsx +0 -59
  36. package/src/components/ticker-bar.tsx +0 -41
  37. package/src/components/tooltip.tsx +0 -62
  38. package/src/components/window-minimized-context.tsx +0 -29
  39. package/src/hooks/useReducedMotion.ts +0 -21
  40. package/src/index.ts +0 -58
  41. package/src/lib/animations.ts +0 -50
  42. package/src/primitives/collapsible-sidebar.tsx +0 -226
  43. package/src/primitives/dialog.tsx +0 -76
  44. package/src/primitives/empty-state.tsx +0 -43
  45. package/src/primitives/index.ts +0 -22
  46. package/src/primitives/list-view.tsx +0 -155
  47. package/src/primitives/property-panel.tsx +0 -108
  48. package/src/primitives/section-header.tsx +0 -19
  49. package/src/primitives/sidebar-nav.tsx +0 -110
  50. package/src/primitives/split-pane.tsx +0 -146
  51. package/src/primitives/status-badge.tsx +0 -10
  52. package/src/primitives/status-bar.tsx +0 -100
  53. package/src/primitives/toolbar.tsx +0 -152
  54. package/src/server.ts +0 -4
  55. package/src/stores/notification-store.ts +0 -271
  56. package/src/stores/theme-store.ts +0 -33
  57. package/src/tokens/lp-tokens.ts +0 -36
  58. package/src/utils.ts +0 -6
  59. package/tsconfig.json +0 -17
@@ -1,233 +0,0 @@
1
- 'use client';
2
-
3
- import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
4
- import * as React from 'react';
5
- import { cn } from '../utils';
6
-
7
- const DropdownMenu = DropdownMenuPrimitive.Root;
8
- const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
9
- const DropdownMenuGroup = DropdownMenuPrimitive.Group;
10
- const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
11
- const DropdownMenuSub = DropdownMenuPrimitive.Sub;
12
- const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
13
-
14
- const DropdownMenuSubTrigger = React.forwardRef<
15
- React.ComponentRef<typeof DropdownMenuPrimitive.SubTrigger>,
16
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
17
- inset?: boolean;
18
- }
19
- >(({ className, inset, children, ...props }, ref) => (
20
- <DropdownMenuPrimitive.SubTrigger
21
- ref={ref}
22
- className={cn(
23
- 'flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-copy-13 outline-none select-none',
24
- 'focus:bg-[var(--khal-menu-hover)]',
25
- 'data-[state=open]:bg-[var(--khal-menu-hover)]',
26
- inset && 'pl-8',
27
- className
28
- )}
29
- style={{ color: 'var(--khal-text-primary)' }}
30
- {...props}
31
- >
32
- {children}
33
- </DropdownMenuPrimitive.SubTrigger>
34
- ));
35
- DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
36
-
37
- const DropdownMenuSubContent = React.forwardRef<
38
- React.ComponentRef<typeof DropdownMenuPrimitive.SubContent>,
39
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
40
- >(({ className, ...props }, ref) => (
41
- <DropdownMenuPrimitive.SubContent
42
- ref={ref}
43
- className={cn(
44
- 'z-[9999] min-w-[8rem] overflow-hidden rounded-xl p-1',
45
- 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
46
- 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
47
- 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
48
- className
49
- )}
50
- style={{
51
- background: 'var(--khal-menu-bg)',
52
- border: '1px solid var(--khal-menu-border)',
53
- boxShadow: 'var(--khal-menu-shadow)',
54
- color: 'var(--khal-text-primary)',
55
- }}
56
- {...props}
57
- />
58
- ));
59
- DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
60
-
61
- const DropdownMenuContent = React.forwardRef<
62
- React.ComponentRef<typeof DropdownMenuPrimitive.Content>,
63
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
64
- >(({ className, sideOffset = 4, ...props }, ref) => (
65
- <DropdownMenuPrimitive.Portal>
66
- <DropdownMenuPrimitive.Content
67
- ref={ref}
68
- sideOffset={sideOffset}
69
- className={cn(
70
- 'z-[9999] min-w-[8rem] overflow-hidden rounded-xl p-1',
71
- 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
72
- 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
73
- 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
74
- className
75
- )}
76
- style={{
77
- background: 'var(--khal-menu-bg)',
78
- border: '1px solid var(--khal-menu-border)',
79
- boxShadow: 'var(--khal-menu-shadow)',
80
- color: 'var(--khal-text-primary)',
81
- }}
82
- {...props}
83
- />
84
- </DropdownMenuPrimitive.Portal>
85
- ));
86
- DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
87
-
88
- const DropdownMenuItem = React.forwardRef<
89
- React.ComponentRef<typeof DropdownMenuPrimitive.Item>,
90
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
91
- inset?: boolean;
92
- prefix?: React.ReactNode;
93
- suffix?: React.ReactNode;
94
- }
95
- >(({ className, inset, prefix, suffix, children, ...props }, ref) => (
96
- <DropdownMenuPrimitive.Item
97
- ref={ref}
98
- className={cn(
99
- 'relative flex cursor-default items-center gap-2 rounded-lg px-2 py-1.5 text-copy-13 outline-none select-none transition-colors',
100
- 'focus:bg-[var(--khal-menu-hover)]',
101
- 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
102
- inset && 'pl-8',
103
- className
104
- )}
105
- style={{ color: 'var(--khal-text-primary)' }}
106
- {...props}
107
- >
108
- {prefix && <span className="inline-flex shrink-0">{prefix}</span>}
109
- {children}
110
- {suffix && (
111
- <span className="ml-auto inline-flex shrink-0" style={{ color: 'var(--khal-text-muted)' }}>
112
- {suffix}
113
- </span>
114
- )}
115
- </DropdownMenuPrimitive.Item>
116
- ));
117
- DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
118
-
119
- const DropdownMenuCheckboxItem = React.forwardRef<
120
- React.ComponentRef<typeof DropdownMenuPrimitive.CheckboxItem>,
121
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
122
- >(({ className, children, checked, ...props }, ref) => (
123
- <DropdownMenuPrimitive.CheckboxItem
124
- ref={ref}
125
- className={cn(
126
- 'relative flex cursor-default items-center rounded-lg py-1.5 pl-8 pr-2 text-copy-13 outline-none select-none transition-colors',
127
- 'focus:bg-[var(--khal-menu-hover)]',
128
- 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
129
- className
130
- )}
131
- style={{ color: 'var(--khal-text-primary)' }}
132
- checked={checked}
133
- {...props}
134
- >
135
- <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
136
- <DropdownMenuPrimitive.ItemIndicator>
137
- <svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
138
- <path
139
- d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3354 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.5553 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z"
140
- fill="currentColor"
141
- fillRule="evenodd"
142
- clipRule="evenodd"
143
- ></path>
144
- </svg>
145
- </DropdownMenuPrimitive.ItemIndicator>
146
- </span>
147
- {children}
148
- </DropdownMenuPrimitive.CheckboxItem>
149
- ));
150
- DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
151
-
152
- const DropdownMenuRadioItem = React.forwardRef<
153
- React.ComponentRef<typeof DropdownMenuPrimitive.RadioItem>,
154
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
155
- >(({ className, children, ...props }, ref) => (
156
- <DropdownMenuPrimitive.RadioItem
157
- ref={ref}
158
- className={cn(
159
- 'relative flex cursor-default items-center rounded-lg py-1.5 pl-8 pr-2 text-copy-13 outline-none select-none transition-colors',
160
- 'focus:bg-[var(--khal-menu-hover)]',
161
- 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
162
- className
163
- )}
164
- style={{ color: 'var(--khal-text-primary)' }}
165
- {...props}
166
- >
167
- <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
168
- <DropdownMenuPrimitive.ItemIndicator>
169
- <svg width="8" height="8" viewBox="0 0 8 8" fill="none">
170
- <circle cx="4" cy="4" r="4" fill="currentColor" />
171
- </svg>
172
- </DropdownMenuPrimitive.ItemIndicator>
173
- </span>
174
- {children}
175
- </DropdownMenuPrimitive.RadioItem>
176
- ));
177
- DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
178
-
179
- const DropdownMenuLabel = React.forwardRef<
180
- React.ComponentRef<typeof DropdownMenuPrimitive.Label>,
181
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
182
- inset?: boolean;
183
- }
184
- >(({ className, inset, ...props }, ref) => (
185
- <DropdownMenuPrimitive.Label
186
- ref={ref}
187
- className={cn('px-2 py-1.5 text-label-12 font-semibold', inset && 'pl-8', className)}
188
- style={{ color: 'var(--khal-text-secondary)' }}
189
- {...props}
190
- />
191
- ));
192
- DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
193
-
194
- const DropdownMenuSeparator = React.forwardRef<
195
- React.ComponentRef<typeof DropdownMenuPrimitive.Separator>,
196
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
197
- >(({ className, ...props }, ref) => (
198
- <DropdownMenuPrimitive.Separator
199
- ref={ref}
200
- className={cn('-mx-1 my-1 h-px', className)}
201
- style={{ background: 'var(--khal-border-default)' }}
202
- {...props}
203
- />
204
- ));
205
- DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
206
-
207
- const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
208
- return (
209
- <span
210
- className={cn('ml-auto text-label-12 tracking-widest', className)}
211
- style={{ color: 'var(--khal-text-muted)' }}
212
- {...props}
213
- />
214
- );
215
- };
216
-
217
- export {
218
- DropdownMenu,
219
- DropdownMenuCheckboxItem,
220
- DropdownMenuContent,
221
- DropdownMenuGroup,
222
- DropdownMenuItem,
223
- DropdownMenuLabel,
224
- DropdownMenuPortal,
225
- DropdownMenuRadioGroup,
226
- DropdownMenuRadioItem,
227
- DropdownMenuSeparator,
228
- DropdownMenuShortcut,
229
- DropdownMenuSub,
230
- DropdownMenuSubContent,
231
- DropdownMenuSubTrigger,
232
- DropdownMenuTrigger,
233
- };
@@ -1,74 +0,0 @@
1
- 'use client';
2
-
3
- import { cva, type VariantProps } from 'class-variance-authority';
4
- import * as React from 'react';
5
- import { cn } from '../utils';
6
-
7
- const glassCardVariants = cva(
8
- 'relative overflow-hidden border transition-all [background:var(--khal-glass-tint)] [border-color:var(--khal-glass-border)]',
9
- {
10
- variants: {
11
- variant: {
12
- default: '[box-shadow:var(--khal-shadow-sm)]',
13
- raised: '[box-shadow:var(--khal-shadow-md)]',
14
- },
15
- padding: {
16
- sm: 'p-3',
17
- md: 'p-4',
18
- lg: 'p-6',
19
- },
20
- },
21
- defaultVariants: {
22
- variant: 'default',
23
- padding: 'md',
24
- },
25
- }
26
- );
27
-
28
- interface GlassCardProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof glassCardVariants> {
29
- hover?: boolean;
30
- glow?: string;
31
- }
32
-
33
- const GlassCard = React.forwardRef<HTMLDivElement, GlassCardProps>(
34
- ({ className, variant, padding, hover = false, glow, style, children, ...props }, ref) => {
35
- return (
36
- <div
37
- ref={ref}
38
- className={cn(
39
- glassCardVariants({ variant, padding }),
40
- hover && [
41
- 'cursor-pointer',
42
- 'hover:-translate-y-0.5',
43
- 'hover:[border-color:var(--khal-border-strong)]',
44
- 'hover:[box-shadow:var(--khal-shadow-lg)]',
45
- ],
46
- className
47
- )}
48
- style={{
49
- backdropFilter: 'var(--khal-glass-filter)',
50
- WebkitBackdropFilter: 'var(--khal-glass-filter)',
51
- borderRadius: 'var(--khal-radius-xl)',
52
- transitionTimingFunction: 'var(--khal-ease-spring)',
53
- transitionDuration: 'var(--khal-duration-normal)',
54
- ...style,
55
- }}
56
- {...props}
57
- >
58
- {glow && (
59
- <div
60
- className="pointer-events-none absolute inset-0 rounded-[inherit]"
61
- style={{
62
- background: `radial-gradient(ellipse at 50% 0%, color-mix(in srgb, ${glow} 20%, transparent), transparent 70%)`,
63
- }}
64
- />
65
- )}
66
- <div className="relative">{children}</div>
67
- </div>
68
- );
69
- }
70
- );
71
- GlassCard.displayName = 'GlassCard';
72
-
73
- export type { GlassCardProps };
74
- export { GlassCard, glassCardVariants };
@@ -1,48 +0,0 @@
1
- 'use client';
2
-
3
- import * as React from 'react';
4
- import { cn } from '../utils';
5
-
6
- interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
7
- /** Label above the input (geistcn compat) */
8
- label?: string;
9
- /** Size variant (geistcn compat) */
10
- size?: 'small' | 'medium' | 'large';
11
- /** HTML input type (named typeName for geistcn compat, also accepts type) */
12
- typeName?: string;
13
- }
14
-
15
- const Input = React.forwardRef<HTMLInputElement, InputProps>(
16
- ({ className, label, size, typeName, type, ...props }, ref) => {
17
- const input = (
18
- <input
19
- type={typeName ?? type}
20
- className={cn(
21
- 'flex w-full rounded-md border border-gray-alpha-400 bg-background-100 px-3 text-copy-13 text-gray-1000 transition-colors',
22
- 'file:border-0 file:bg-transparent file:text-sm file:font-medium',
23
- 'placeholder:text-gray-700',
24
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-700 focus-visible:ring-offset-1',
25
- 'disabled:cursor-not-allowed disabled:opacity-50',
26
- size === 'small' ? 'h-8' : size === 'large' ? 'h-11' : 'h-9',
27
- className
28
- )}
29
- ref={ref}
30
- {...props}
31
- />
32
- );
33
-
34
- if (label) {
35
- return (
36
- <div className="flex flex-col gap-1.5">
37
- <label className="text-label-13 text-gray-900">{label}</label>
38
- {input}
39
- </div>
40
- );
41
- }
42
-
43
- return input;
44
- }
45
- );
46
- Input.displayName = 'Input';
47
-
48
- export { Input };
@@ -1,73 +0,0 @@
1
- interface KhalLogoProps {
2
- size?: number;
3
- variant?: 'light' | 'dark';
4
- className?: string;
5
- }
6
-
7
- /**
8
- * KhalLogo — SVG wordmark for large sizes (>=20), single "K" letterform for small sizes.
9
- * Uses currentColor so color is inherited from parent.
10
- */
11
- export function KhalLogo({ size = 20, variant = 'light', className }: KhalLogoProps) {
12
- const color = variant === 'light' ? '#FFFFFF' : '#0A0A0A';
13
- const showFull = size >= 20;
14
-
15
- if (!showFull) {
16
- // Small sizes: render just the K letterform from the SVG
17
- return (
18
- <svg
19
- viewBox="0 0 156 155"
20
- fill="none"
21
- xmlns="http://www.w3.org/2000/svg"
22
- width={size}
23
- height={size}
24
- className={className}
25
- aria-label="K"
26
- style={{ color }}
27
- >
28
- <path
29
- d="M0 0H27.4425V65.9519H71.7054L122.829 0H155.362L95.3869 76.1317L164.657 154.92H128.805L72.5913 92.2878H27.4425V154.92H0V0Z"
30
- fill="currentColor"
31
- />
32
- </svg>
33
- );
34
- }
35
-
36
- // Full wordmark SVG
37
- return (
38
- <svg
39
- viewBox="0 0 756 155"
40
- fill="none"
41
- xmlns="http://www.w3.org/2000/svg"
42
- width={size * (756 / 155)}
43
- height={size}
44
- className={className}
45
- aria-label="khal"
46
- style={{ color }}
47
- >
48
- <g clipPath="url(#khal-logo-clip)">
49
- <path
50
- d="M616.81 0H644.252V128.584H765.533V154.92H638.499C635.4 154.92 632.524 154.33 629.867 153.149C627.211 151.969 624.924 150.42 623.007 148.502C621.088 146.584 619.539 144.371 618.359 141.863C617.326 139.206 616.81 136.403 616.81 133.453V0Z"
51
- fill="currentColor"
52
- />
53
- <path
54
- d="M443.058 21.2467C445.123 14.4594 448.295 9.22105 452.573 5.53287C456.853 1.8447 462.533 0 469.616 0H519.632C527.009 0 532.911 1.91744 537.337 5.75467C541.763 9.44285 545.009 14.6072 547.076 21.2467L589.125 154.92H560.133L546.411 110.657H461.87L468.73 84.3212H538.665L521.181 26.3359H468.951L430 154.92H400.786L443.058 21.2467Z"
55
- fill="currentColor"
56
- />
57
- <path
58
- d="M190.123 0H217.565V62.6322H344.6V0H372.043V154.92H344.6V88.9681H217.565V154.92H190.123V0Z"
59
- fill="currentColor"
60
- />
61
- <path
62
- d="M0 0H27.4425V65.9519H71.7054L122.829 0H155.362L95.3869 76.1317L164.657 154.92H128.805L72.5913 92.2878H27.4425V154.92H0V0Z"
63
- fill="currentColor"
64
- />
65
- </g>
66
- <defs>
67
- <clipPath id="khal-logo-clip">
68
- <rect width="756" height="155" fill="white" />
69
- </clipPath>
70
- </defs>
71
- </svg>
72
- );
73
- }
@@ -1,109 +0,0 @@
1
- 'use client';
2
-
3
- import { AnimatePresence, motion } from 'motion/react';
4
- import { useCallback, useEffect, useRef, useState } from 'react';
5
- import { cn } from '../utils';
6
-
7
- type FeedEventType = 'info' | 'success' | 'warning' | 'error' | 'agent' | 'system';
8
-
9
- interface FeedEvent {
10
- id: string;
11
- type: FeedEventType;
12
- message: string;
13
- timestamp?: Date;
14
- }
15
-
16
- const typeColors: Record<FeedEventType, string> = {
17
- info: 'var(--ds-blue-600)',
18
- success: 'var(--ds-green-600)',
19
- warning: 'var(--ds-amber-600)',
20
- error: 'var(--ds-red-600)',
21
- agent: 'var(--ds-purple-600)',
22
- system: 'var(--ds-gray-600)',
23
- };
24
-
25
- interface LiveFeedProps {
26
- /** Initial events to render */
27
- events?: FeedEvent[];
28
- /** Maximum visible events before oldest are removed */
29
- maxVisible?: number;
30
- /** Show timestamps next to events */
31
- showTimestamps?: boolean;
32
- /** Height of the feed container */
33
- height?: number | string;
34
- className?: string;
35
- }
36
-
37
- function LiveFeed({
38
- events: externalEvents,
39
- maxVisible = 50,
40
- showTimestamps = true,
41
- height = 300,
42
- className,
43
- }: LiveFeedProps) {
44
- const [events, setEvents] = useState<FeedEvent[]>(externalEvents ?? []);
45
- const scrollRef = useRef<HTMLDivElement>(null);
46
- const isAtBottom = useRef(true);
47
-
48
- // Sync external events
49
- useEffect(() => {
50
- if (externalEvents) {
51
- setEvents((prev) => {
52
- const combined = [...prev, ...externalEvents.filter((e) => !prev.some((p) => p.id === e.id))];
53
- return combined.slice(-maxVisible);
54
- });
55
- }
56
- }, [externalEvents, maxVisible]);
57
-
58
- // Auto-scroll to bottom when new events arrive and user is at bottom
59
- useEffect(() => {
60
- const el = scrollRef.current;
61
- if (el && isAtBottom.current) {
62
- el.scrollTop = el.scrollHeight;
63
- }
64
- }, [events]);
65
-
66
- const handleScroll = useCallback(() => {
67
- const el = scrollRef.current;
68
- if (el) {
69
- isAtBottom.current = el.scrollHeight - el.scrollTop - el.clientHeight < 24;
70
- }
71
- }, []);
72
-
73
- const formatTime = (d: Date) =>
74
- d.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
75
-
76
- return (
77
- <div
78
- ref={scrollRef}
79
- onScroll={handleScroll}
80
- className={cn('overflow-y-auto overflow-x-hidden font-mono text-xs leading-5 scrollbar-thin', className)}
81
- style={{ height }}
82
- >
83
- <AnimatePresence initial={false}>
84
- {events.map((event) => (
85
- <motion.div
86
- key={event.id}
87
- initial={{ opacity: 0, height: 0 }}
88
- animate={{ opacity: 1, height: 'auto' }}
89
- exit={{ opacity: 0, height: 0 }}
90
- transition={{ duration: 0.25, ease: [0.22, 1, 0.36, 1] }}
91
- className="flex gap-2 px-2 py-0.5 hover:bg-[var(--ds-gray-alpha-100)]"
92
- >
93
- <span
94
- className="inline-block w-1.5 h-1.5 rounded-full mt-1.5 shrink-0"
95
- style={{ backgroundColor: typeColors[event.type] }}
96
- />
97
- {showTimestamps && event.timestamp && (
98
- <span className="shrink-0 opacity-40 tabular-nums">{formatTime(event.timestamp)}</span>
99
- )}
100
- <span className="opacity-80 break-words min-w-0">{event.message}</span>
101
- </motion.div>
102
- ))}
103
- </AnimatePresence>
104
- </div>
105
- );
106
- }
107
-
108
- export type { LiveFeedProps, FeedEvent, FeedEventType };
109
- export { LiveFeed };
@@ -1,57 +0,0 @@
1
- 'use client';
2
-
3
- import { lazy, Suspense, useEffect, useRef, useState } from 'react';
4
- import { useReducedMotion } from '../hooks/useReducedMotion';
5
-
6
- const MeshGradientShader = lazy(() => import('@paper-design/shaders-react').then((m) => ({ default: m.MeshGradient })));
7
-
8
- interface MeshGradientProps {
9
- /** Array of CSS color strings (typically 4-8) */
10
- colors: string[];
11
- /** Animation speed multiplier (default 0.02) */
12
- speed?: number;
13
- className?: string;
14
- style?: React.CSSProperties;
15
- }
16
-
17
- function StaticFallback({ colors }: { colors: string[] }) {
18
- const bg =
19
- colors.length >= 2
20
- ? `linear-gradient(135deg, ${colors[0]} 0%, ${colors[Math.floor(colors.length / 2)]} 50%, ${colors[colors.length - 1]} 100%)`
21
- : (colors[0] ?? '#0A0A0A');
22
-
23
- return <div style={{ width: '100%', height: '100%', background: bg }} />;
24
- }
25
-
26
- function MeshGradientInner({ colors, speed = 0.02, className, style }: MeshGradientProps) {
27
- const ref = useRef<HTMLDivElement>(null);
28
- const [visible, setVisible] = useState(false);
29
- const reducedMotion = useReducedMotion();
30
-
31
- useEffect(() => {
32
- const el = ref.current;
33
- if (!el) return;
34
-
35
- const observer = new IntersectionObserver(([entry]) => setVisible(entry.isIntersecting), {
36
- rootMargin: '100px',
37
- });
38
-
39
- observer.observe(el);
40
- return () => observer.disconnect();
41
- }, []);
42
-
43
- return (
44
- <div ref={ref} className={className} style={{ position: 'absolute', inset: 0, ...style }}>
45
- {visible && !reducedMotion ? (
46
- <Suspense fallback={<StaticFallback colors={colors} />}>
47
- <MeshGradientShader colors={colors} speed={speed} style={{ width: '100%', height: '100%' }} />
48
- </Suspense>
49
- ) : (
50
- <StaticFallback colors={colors} />
51
- )}
52
- </div>
53
- );
54
- }
55
-
56
- export type { MeshGradientProps };
57
- export { MeshGradientInner as MeshGradient };
@@ -1,93 +0,0 @@
1
- 'use client';
2
-
3
- import { cva, type VariantProps } from 'class-variance-authority';
4
- import * as React from 'react';
5
- import { cn } from '../utils';
6
-
7
- /**
8
- * MetricDisplay — LP section-level metric/stat component.
9
- *
10
- * Extracted from khal-landing section components:
11
- * - Stat cards in metrics.tsx (large numbers with suffix/prefix)
12
- * - Metric tiles in architecture.tsx AppBuilderMockup
13
- * - ROI projected savings in metrics.tsx
14
- *
15
- * Renders a large highlighted value with a label and optional description.
16
- */
17
- const metricDisplayVariants = cva('flex flex-col', {
18
- variants: {
19
- size: {
20
- /** Compact — for inline/tile use (architecture mockup metric tiles) */
21
- sm: 'gap-1',
22
- /** Standard — for dashboard displays */
23
- md: 'gap-1.5',
24
- /** Large — hero stat cards (metrics.tsx) */
25
- lg: 'gap-2',
26
- },
27
- },
28
- defaultVariants: {
29
- size: 'md',
30
- },
31
- });
32
-
33
- const valueSizeMap = {
34
- sm: 'text-[22px] font-semibold leading-7 tracking-tight',
35
- md: 'text-[28px] font-semibold leading-8 tracking-[-0.02em]',
36
- lg: 'text-[36px] sm:text-[44px] font-semibold leading-none tracking-[-0.04em]',
37
- } as const;
38
-
39
- const labelSizeMap = {
40
- sm: 'text-[11px] uppercase tracking-[0.06em] text-[#FFFFFF80] font-medium leading-3.5',
41
- md: 'text-[13px] text-[#FFFFFFCC] font-medium leading-4',
42
- lg: 'text-[15px] text-[#FFFFFFCC] font-medium leading-5',
43
- } as const;
44
-
45
- interface MetricDisplayProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof metricDisplayVariants> {
46
- /** The primary value to display */
47
- value: string | number;
48
- /** Label describing the metric */
49
- label: string;
50
- /** Optional description/subtext below the label */
51
- description?: string;
52
- /** Optional prefix before the value (e.g. "+", "$") */
53
- prefix?: string;
54
- /** Optional suffix after the value (e.g. "%", "pts", "ms") */
55
- suffix?: string;
56
- /** Accent color for the value. Defaults to current text color. */
57
- accentColor?: string;
58
- }
59
-
60
- const MetricDisplay = React.forwardRef<HTMLDivElement, MetricDisplayProps>(
61
- ({ className, size = 'md', value, label, description, prefix, suffix, accentColor, ...props }, ref) => {
62
- const resolvedSize = (size ?? 'md') as 'sm' | 'md' | 'lg';
63
-
64
- return (
65
- <div ref={ref} className={cn(metricDisplayVariants({ size }), className)} {...props}>
66
- {/* Label above value for sm size (matches LP tile pattern) */}
67
- {resolvedSize === 'sm' && <span className={labelSizeMap[resolvedSize]}>{label}</span>}
68
-
69
- {/* Value */}
70
- <div
71
- className={cn(valueSizeMap[resolvedSize], 'tabular-nums')}
72
- style={accentColor ? { color: accentColor } : undefined}
73
- >
74
- {prefix && <span>{prefix}</span>}
75
- <span>{value}</span>
76
- {suffix && (
77
- <span className={resolvedSize === 'lg' ? 'text-[32px] tracking-[-0.02em] ml-0.5' : 'ml-0.5'}>{suffix}</span>
78
- )}
79
- </div>
80
-
81
- {/* Label below value for md/lg sizes */}
82
- {resolvedSize !== 'sm' && <span className={labelSizeMap[resolvedSize]}>{label}</span>}
83
-
84
- {/* Description */}
85
- {description && <span className="text-[13px] text-[#FFFFFF80] leading-4">{description}</span>}
86
- </div>
87
- );
88
- }
89
- );
90
- MetricDisplay.displayName = 'MetricDisplay';
91
-
92
- export type { MetricDisplayProps };
93
- export { MetricDisplay, metricDisplayVariants };