@khal-os/ui 1.0.1 → 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,130 +0,0 @@
1
- 'use client';
2
-
3
- import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
4
- import * as React from 'react';
5
- import { cn } from '../utils';
6
-
7
- const ContextMenu = ContextMenuPrimitive.Root;
8
- const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
9
- const ContextMenuGroup = ContextMenuPrimitive.Group;
10
- const ContextMenuPortal = ContextMenuPrimitive.Portal;
11
- const ContextMenuSub = ContextMenuPrimitive.Sub;
12
- const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
13
-
14
- const ContextMenuSubTrigger = React.forwardRef<
15
- React.ComponentRef<typeof ContextMenuPrimitive.SubTrigger>,
16
- React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
17
- inset?: boolean;
18
- }
19
- >(({ className, inset, children, ...props }, ref) => (
20
- <ContextMenuPrimitive.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
- </ContextMenuPrimitive.SubTrigger>
34
- ));
35
- ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
36
-
37
- const ContextMenuSubContent = React.forwardRef<
38
- React.ComponentRef<typeof ContextMenuPrimitive.SubContent>,
39
- React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
40
- >(({ className, ...props }, ref) => (
41
- <ContextMenuPrimitive.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
- className
48
- )}
49
- style={{
50
- background: 'var(--khal-menu-bg)',
51
- border: '1px solid var(--khal-menu-border)',
52
- boxShadow: 'var(--khal-menu-shadow)',
53
- color: 'var(--khal-text-primary)',
54
- }}
55
- {...props}
56
- />
57
- ));
58
- ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
59
-
60
- const ContextMenuContent = React.forwardRef<
61
- React.ComponentRef<typeof ContextMenuPrimitive.Content>,
62
- React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
63
- >(({ className, ...props }, ref) => (
64
- <ContextMenuPrimitive.Portal>
65
- <ContextMenuPrimitive.Content
66
- ref={ref}
67
- className={cn(
68
- 'z-[9999] min-w-[8rem] overflow-hidden rounded-xl p-1',
69
- 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
70
- className
71
- )}
72
- style={{
73
- background: 'var(--khal-menu-bg)',
74
- border: '1px solid var(--khal-menu-border)',
75
- boxShadow: 'var(--khal-menu-shadow)',
76
- color: 'var(--khal-text-primary)',
77
- }}
78
- {...props}
79
- />
80
- </ContextMenuPrimitive.Portal>
81
- ));
82
- ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
83
-
84
- const ContextMenuItem = React.forwardRef<
85
- React.ComponentRef<typeof ContextMenuPrimitive.Item>,
86
- React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
87
- inset?: boolean;
88
- }
89
- >(({ className, inset, ...props }, ref) => (
90
- <ContextMenuPrimitive.Item
91
- ref={ref}
92
- className={cn(
93
- 'relative flex cursor-default items-center gap-2 rounded-lg px-2 py-1.5 text-copy-13 outline-none select-none transition-colors',
94
- 'focus:bg-[var(--khal-menu-hover)]',
95
- 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
96
- inset && 'pl-8',
97
- className
98
- )}
99
- style={{ color: 'var(--khal-text-primary)' }}
100
- {...props}
101
- />
102
- ));
103
- ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
104
-
105
- const ContextMenuSeparator = React.forwardRef<
106
- React.ComponentRef<typeof ContextMenuPrimitive.Separator>,
107
- React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
108
- >(({ className, ...props }, ref) => (
109
- <ContextMenuPrimitive.Separator
110
- ref={ref}
111
- className={cn('-mx-1 my-1 h-px', className)}
112
- style={{ background: 'var(--khal-border-default)' }}
113
- {...props}
114
- />
115
- ));
116
- ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
117
-
118
- export {
119
- ContextMenu,
120
- ContextMenuContent,
121
- ContextMenuGroup,
122
- ContextMenuItem,
123
- ContextMenuPortal,
124
- ContextMenuRadioGroup,
125
- ContextMenuSeparator,
126
- ContextMenuSub,
127
- ContextMenuSubContent,
128
- ContextMenuSubTrigger,
129
- ContextMenuTrigger,
130
- };
@@ -1,71 +0,0 @@
1
- 'use client';
2
-
3
- import * as React from 'react';
4
- import { cn } from '../utils';
5
- import { StatusDot } from './status-dot';
6
-
7
- const sizeMap = { sm: 24, md: 32, lg: 40 } as const;
8
- const fontSizeMap = { sm: '10px', md: '12px', lg: '14px' } as const;
9
-
10
- const statusColorMap: Record<string, string> = {
11
- online: 'var(--khal-status-live)',
12
- idle: 'var(--khal-status-warning)',
13
- away: 'var(--khal-status-idle)',
14
- };
15
-
16
- interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {
17
- name: string;
18
- size?: keyof typeof sizeMap;
19
- status?: 'online' | 'idle' | 'away' | null;
20
- src?: string;
21
- }
22
-
23
- function getInitials(name: string): string {
24
- const parts = name.trim().split(/\s+/);
25
- if (parts.length >= 2) {
26
- return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
27
- }
28
- return name.charAt(0).toUpperCase();
29
- }
30
-
31
- const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(
32
- ({ name, size = 'md', status, src, className, style, ...props }, ref) => {
33
- const px = sizeMap[size];
34
- const [imgError, setImgError] = React.useState(false);
35
- const showImage = src && !imgError;
36
-
37
- return (
38
- <div
39
- ref={ref}
40
- className={cn('relative inline-flex shrink-0', className)}
41
- style={{ width: px, height: px, ...style }}
42
- {...props}
43
- >
44
- {showImage ? (
45
- <img
46
- src={src}
47
- alt={name}
48
- className="h-full w-full rounded-full object-cover"
49
- onError={() => setImgError(true)}
50
- />
51
- ) : (
52
- <div
53
- className="flex h-full w-full select-none items-center justify-center rounded-full border font-medium [background:var(--khal-surface-raised)] [border-color:var(--khal-border-subtle)]"
54
- style={{ fontSize: fontSizeMap[size] }}
55
- >
56
- {getInitials(name)}
57
- </div>
58
- )}
59
- {status && (
60
- <div className="absolute -bottom-px -right-px">
61
- <StatusDot color={statusColorMap[status]} size="sm" label={status} pulse={status === 'online'} />
62
- </div>
63
- )}
64
- </div>
65
- );
66
- }
67
- );
68
- Avatar.displayName = 'Avatar';
69
-
70
- export type { AvatarProps };
71
- export { Avatar };
@@ -1,39 +0,0 @@
1
- import { cva, type VariantProps } from 'class-variance-authority';
2
- import * as React from 'react';
3
- import { cn } from '../utils';
4
-
5
- const badgeVariants = cva(
6
- 'inline-flex items-center rounded-full border font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-blue-700 focus:ring-offset-2',
7
- {
8
- variants: {
9
- variant: {
10
- gray: 'border-gray-alpha-400 bg-gray-100 text-gray-900',
11
- blue: 'border-blue-300 bg-blue-100 text-blue-900',
12
- green: 'border-green-300 bg-green-100 text-green-900',
13
- amber: 'border-amber-300 bg-amber-100 text-amber-900',
14
- red: 'border-red-300 bg-red-100 text-red-900',
15
- purple: 'border-purple-300 bg-purple-100 text-purple-900',
16
- pink: 'border-pink-300 bg-pink-100 text-pink-900',
17
- teal: 'border-teal-300 bg-teal-100 text-teal-900',
18
- },
19
- size: {
20
- sm: 'px-2 py-0 text-[11px] leading-[18px]',
21
- md: 'px-2.5 py-0.5 text-[12px] leading-[18px]',
22
- },
23
- },
24
- defaultVariants: {
25
- variant: 'gray',
26
- size: 'md',
27
- },
28
- }
29
- );
30
-
31
- interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof badgeVariants> {
32
- contrast?: 'low' | 'high';
33
- }
34
-
35
- function Badge({ className, variant, size, contrast, ...props }: BadgeProps) {
36
- return <span className={cn(badgeVariants({ variant, size }), className)} {...props} />;
37
- }
38
-
39
- export { Badge, badgeVariants };
@@ -1,102 +0,0 @@
1
- 'use client';
2
-
3
- import { Slot } from '@radix-ui/react-slot';
4
- import { cva, type VariantProps } from 'class-variance-authority';
5
- import * as React from 'react';
6
- import { cn } from '../utils';
7
- import { Spinner } from './spinner';
8
-
9
- const buttonVariants = cva(
10
- 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[var(--khal-radius-button,10px)] text-copy-13 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--khal-accent-primary)] focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 cursor-pointer',
11
- {
12
- variants: {
13
- variant: {
14
- default: 'bg-gray-1000 text-white [color:white] dark:text-black dark:[color:black] hover:bg-gray-900',
15
- secondary: 'bg-background-100 text-gray-1000 border border-gray-alpha-400 hover:bg-gray-alpha-100',
16
- tertiary: 'bg-transparent text-gray-1000 hover:bg-gray-alpha-200',
17
- error: 'bg-red-700 text-white [color:white] hover:bg-red-600',
18
- warning: 'bg-amber-700 text-white [color:white] hover:bg-amber-600',
19
- ghost: 'hover:bg-gray-alpha-200 text-gray-1000',
20
- link: 'text-blue-700 underline-offset-4 hover:underline',
21
- },
22
- size: {
23
- small: 'h-8 px-3 text-copy-13',
24
- medium: 'h-9 px-4 text-copy-13',
25
- large: 'h-10 px-5 text-copy-14',
26
- icon: 'h-9 w-9',
27
- },
28
- },
29
- defaultVariants: {
30
- variant: 'default',
31
- size: 'medium',
32
- },
33
- }
34
- );
35
-
36
- type ButtonVariantProps = VariantProps<typeof buttonVariants>;
37
-
38
- interface ButtonProps
39
- extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'type' | 'prefix'>,
40
- ButtonVariantProps {
41
- asChild?: boolean;
42
- loading?: boolean;
43
- prefix?: React.ReactNode;
44
- suffix?: React.ReactNode;
45
- /** HTML button type attribute (named typeName for compat with geistcn API) */
46
- typeName?: 'submit' | 'button' | 'reset';
47
- /** Visual type — maps to variant for compat */
48
- type?: 'shadow' | 'invert' | 'unstyled';
49
- }
50
-
51
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
52
- (
53
- {
54
- className,
55
- variant,
56
- size,
57
- asChild = false,
58
- loading = false,
59
- prefix,
60
- suffix,
61
- typeName,
62
- type,
63
- disabled,
64
- children,
65
- ...props
66
- },
67
- ref
68
- ) => {
69
- // Map geistcn "type" prop to variant if variant not explicitly set
70
- let resolvedVariant = variant;
71
- if (!resolvedVariant && type) {
72
- if (type === 'invert') resolvedVariant = 'default';
73
- else if (type === 'shadow') resolvedVariant = 'secondary';
74
- else if (type === 'unstyled') resolvedVariant = 'ghost';
75
- }
76
-
77
- const Comp = asChild ? Slot : 'button';
78
- return (
79
- <Comp
80
- className={cn(buttonVariants({ variant: resolvedVariant, size, className }))}
81
- ref={ref}
82
- type={typeName ?? 'button'}
83
- disabled={disabled || loading}
84
- {...props}
85
- >
86
- {loading ? (
87
- <Spinner size="sm" />
88
- ) : (
89
- <>
90
- {prefix && <span className="inline-flex shrink-0">{prefix}</span>}
91
- {children}
92
- {suffix && <span className="inline-flex shrink-0">{suffix}</span>}
93
- </>
94
- )}
95
- </Comp>
96
- );
97
- }
98
- );
99
- Button.displayName = 'Button';
100
-
101
- export type { ButtonProps };
102
- export { Button, buttonVariants };
@@ -1,165 +0,0 @@
1
- 'use client';
2
-
3
- import * as DialogPrimitive from '@radix-ui/react-dialog';
4
- import { Command as CommandPrimitive } from 'cmdk';
5
- import { Search } from 'lucide-react';
6
- import * as React from 'react';
7
- import { cn } from '../utils';
8
-
9
- const Command = React.forwardRef<
10
- React.ComponentRef<typeof CommandPrimitive>,
11
- React.ComponentPropsWithoutRef<typeof CommandPrimitive>
12
- >(({ className, ...props }, ref) => (
13
- <CommandPrimitive
14
- ref={ref}
15
- className={cn('flex h-full w-full flex-col overflow-hidden rounded-xl bg-background-100 text-gray-1000', className)}
16
- {...props}
17
- />
18
- ));
19
- Command.displayName = CommandPrimitive.displayName;
20
-
21
- function CommandDialog({
22
- children,
23
- open,
24
- onOpenChange,
25
- ...props
26
- }: React.ComponentProps<typeof DialogPrimitive.Root> & React.ComponentPropsWithoutRef<typeof CommandPrimitive>) {
27
- return (
28
- <DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>
29
- <DialogPrimitive.Portal>
30
- <DialogPrimitive.Overlay
31
- className="fixed inset-0 z-[9999] bg-black/40 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0"
32
- cmdk-overlay=""
33
- />
34
- <DialogPrimitive.Content
35
- className="fixed left-[50%] top-[20%] z-[9999] w-full max-w-lg translate-x-[-50%] overflow-hidden rounded-xl shadow-lg"
36
- style={{
37
- background: 'var(--khal-menu-bg)',
38
- border: '1px solid var(--khal-menu-border)',
39
- boxShadow: 'var(--khal-menu-shadow)',
40
- backdropFilter: 'blur(24px)',
41
- WebkitBackdropFilter: 'blur(24px)',
42
- }}
43
- cmdk-dialog=""
44
- aria-describedby={undefined}
45
- >
46
- <DialogPrimitive.Title className="sr-only">Command palette</DialogPrimitive.Title>
47
- <Command
48
- className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-gray-700 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3"
49
- {...props}
50
- >
51
- {children}
52
- </Command>
53
- </DialogPrimitive.Content>
54
- </DialogPrimitive.Portal>
55
- </DialogPrimitive.Root>
56
- );
57
- }
58
-
59
- const CommandInput = React.forwardRef<
60
- React.ComponentRef<typeof CommandPrimitive.Input>,
61
- React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
62
- >(({ className, ...props }, ref) => (
63
- <div
64
- className="flex items-center px-3"
65
- style={{ borderBottom: '1px solid var(--khal-border-default)' }}
66
- cmdk-input-wrapper=""
67
- >
68
- <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
69
- <CommandPrimitive.Input
70
- ref={ref}
71
- className={cn(
72
- 'flex h-10 w-full rounded-md bg-transparent py-3 text-copy-13 outline-none',
73
- 'placeholder:text-gray-700',
74
- 'disabled:cursor-not-allowed disabled:opacity-50',
75
- className
76
- )}
77
- {...props}
78
- />
79
- </div>
80
- ));
81
- CommandInput.displayName = CommandPrimitive.Input.displayName;
82
-
83
- const CommandList = React.forwardRef<
84
- React.ComponentRef<typeof CommandPrimitive.List>,
85
- React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
86
- >(({ className, ...props }, ref) => (
87
- <CommandPrimitive.List
88
- ref={ref}
89
- className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden', className)}
90
- {...props}
91
- />
92
- ));
93
- CommandList.displayName = CommandPrimitive.List.displayName;
94
-
95
- const CommandEmpty = React.forwardRef<
96
- React.ComponentRef<typeof CommandPrimitive.Empty>,
97
- React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
98
- >((props, ref) => (
99
- <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-copy-13 text-gray-700" {...props} />
100
- ));
101
- CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
102
-
103
- const CommandGroup = React.forwardRef<
104
- React.ComponentRef<typeof CommandPrimitive.Group>,
105
- React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
106
- >(({ className, ...props }, ref) => (
107
- <CommandPrimitive.Group
108
- ref={ref}
109
- className={cn(
110
- 'overflow-hidden p-1 text-gray-1000',
111
- '[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-label-12 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-gray-700',
112
- className
113
- )}
114
- {...props}
115
- />
116
- ));
117
- CommandGroup.displayName = CommandPrimitive.Group.displayName;
118
-
119
- const CommandSeparator = React.forwardRef<
120
- React.ComponentRef<typeof CommandPrimitive.Separator>,
121
- React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
122
- >(({ className, ...props }, ref) => (
123
- <CommandPrimitive.Separator ref={ref} className={cn('-mx-1 h-px bg-gray-alpha-400', className)} {...props} />
124
- ));
125
- CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
126
-
127
- const CommandItem = React.forwardRef<
128
- React.ComponentRef<typeof CommandPrimitive.Item>,
129
- React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item> & {
130
- prefix?: React.ReactNode;
131
- callback?: () => void;
132
- }
133
- >(({ className, prefix, callback, onSelect, children, ...props }, ref) => (
134
- <CommandPrimitive.Item
135
- ref={ref}
136
- onSelect={onSelect ?? callback}
137
- className={cn(
138
- 'relative flex cursor-default items-center gap-2 rounded-lg px-2 py-1.5 text-copy-13 outline-none select-none',
139
- 'data-[selected=true]:text-gray-1000',
140
- 'data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50',
141
- className
142
- )}
143
- {...props}
144
- >
145
- {prefix && <span className="inline-flex shrink-0">{prefix}</span>}
146
- {children}
147
- </CommandPrimitive.Item>
148
- ));
149
- CommandItem.displayName = CommandPrimitive.Item.displayName;
150
-
151
- const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
152
- return <span className={cn('ml-auto text-label-12 tracking-widest text-gray-700', className)} {...props} />;
153
- };
154
-
155
- export {
156
- Command,
157
- CommandDialog,
158
- CommandEmpty,
159
- CommandGroup,
160
- CommandInput,
161
- CommandItem,
162
- CommandList,
163
- CommandSeparator,
164
- CommandShortcut,
165
- };
@@ -1,75 +0,0 @@
1
- 'use client';
2
-
3
- import { motion, useInView } from 'motion/react';
4
- import { useCallback, useRef, useState } from 'react';
5
- import { cn } from '../utils';
6
- import { NumberFlow } from './number-flow';
7
-
8
- interface CostCounterProps {
9
- /** The target numeric value to animate to */
10
- value: number;
11
- /** Text suffix after the number (e.g. "%", "pts", "k") */
12
- suffix?: string;
13
- /** Text prefix before the number (e.g. "$", "+") */
14
- prefix?: string;
15
- /** Primary label below the number */
16
- label: string;
17
- /** Secondary description text */
18
- description?: string;
19
- /** Budget bar target percentage (0-100). Omit to hide the bar. */
20
- budget?: number;
21
- /** Budget bar color — defaults to accent-warm */
22
- budgetColor?: string;
23
- className?: string;
24
- }
25
-
26
- function CostCounter({ value, suffix, prefix, label, description, budget, budgetColor, className }: CostCounterProps) {
27
- const [displayed, setDisplayed] = useState(0);
28
- const triggered = useRef(false);
29
- const barRef = useRef<HTMLDivElement>(null);
30
- const barInView = useInView(barRef, { once: true, amount: 0.6 });
31
-
32
- const handleViewport = useCallback(() => {
33
- if (!triggered.current) {
34
- triggered.current = true;
35
- setDisplayed(value);
36
- }
37
- }, [value]);
38
-
39
- return (
40
- <motion.div
41
- onViewportEnter={handleViewport}
42
- viewport={{ once: true, amount: 0.5 }}
43
- className={cn('flex flex-col gap-3', className)}
44
- >
45
- <div
46
- className="flex items-baseline tabular-nums font-semibold tracking-tight leading-none"
47
- style={{ fontFamily: 'var(--font-display, var(--font-geist-sans))' }}
48
- >
49
- {prefix && <span className="text-[0.7em]">{prefix}</span>}
50
- <NumberFlow value={displayed} />
51
- {suffix && <span className="text-[0.65em] ml-0.5 opacity-70">{suffix}</span>}
52
- </div>
53
-
54
- {budget != null && (
55
- <div ref={barRef} className="w-full h-2 rounded-full overflow-hidden bg-[var(--ds-gray-alpha-200)]">
56
- <motion.div
57
- initial={{ width: 0 }}
58
- animate={barInView ? { width: `${Math.min(budget, 100)}%` } : { width: 0 }}
59
- transition={{ duration: 1, ease: [0.22, 1, 0.36, 1], delay: 0.15 }}
60
- className="h-full rounded-full"
61
- style={{ backgroundColor: budgetColor ?? 'var(--ds-accent-warm)' }}
62
- />
63
- </div>
64
- )}
65
-
66
- <div className="flex flex-col gap-0.5">
67
- <span className="text-sm font-medium leading-5">{label}</span>
68
- {description && <span className="text-xs opacity-50 leading-4">{description}</span>}
69
- </div>
70
- </motion.div>
71
- );
72
- }
73
-
74
- export type { CostCounterProps };
75
- export { CostCounter };
@@ -1,97 +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
- * DataRow — LP section-level key-value / data display component.
9
- *
10
- * Extracted from khal-landing section components:
11
- * - Architecture.tsx: font-mono code rules (IF/THEN/ELSE patterns)
12
- * - Architecture.tsx: connection rows with status dots
13
- * - Omnichannel-spotlight.tsx: channel count rows (font-mono tabular-nums)
14
- * - Omnichannel-spotlight.tsx: observability inline data
15
- *
16
- * Renders a horizontal key-value pair with monospace font and optional accent.
17
- */
18
- const dataRowVariants = cva('flex items-center gap-3 rounded-lg border border-[#FFFFFF14] font-mono', {
19
- variants: {
20
- variant: {
21
- /** Standard data row — subtle bg */
22
- default: 'bg-[#FFFFFF08] py-2.5 px-3',
23
- /** Inline/compact — for observability-style data */
24
- inline: 'bg-[#FFFFFF06] py-1.5 px-2.5 text-[11px]',
25
- /** Nested rule row — for indented rule displays */
26
- rule: 'bg-[#FFFFFF05] py-2.5 px-3',
27
- },
28
- },
29
- defaultVariants: {
30
- variant: 'default',
31
- },
32
- });
33
-
34
- interface DataRowProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof dataRowVariants> {
35
- /** The label/key portion (left side) */
36
- label: string;
37
- /** The value portion (right side, pushed to end) */
38
- value?: string;
39
- /** Accent color for the value text */
40
- accentColor?: string;
41
- /** Show a status dot before the label */
42
- statusDot?: boolean;
43
- /** Custom status dot color */
44
- dotColor?: string;
45
- /** Optional tag/badge to show before the label (e.g. "IF", "THEN") */
46
- tag?: string;
47
- /** Tag color — defaults to accent */
48
- tagColor?: string;
49
- }
50
-
51
- const DataRow = React.forwardRef<HTMLDivElement, DataRowProps>(
52
- ({ className, variant, label, value, accentColor, statusDot, dotColor, tag, tagColor, children, ...props }, ref) => {
53
- return (
54
- <div ref={ref} className={cn(dataRowVariants({ variant }), className)} {...props}>
55
- {/* Status dot */}
56
- {statusDot && (
57
- <span className="size-[6px] shrink-0 rounded-full" style={{ backgroundColor: dotColor || '#FFFFFF40' }} />
58
- )}
59
-
60
- {/* Tag badge (IF/THEN/ELSE style) */}
61
- {tag && (
62
- <span
63
- className="shrink-0 rounded py-0.5 px-2 text-[10px] font-bold uppercase tracking-wide leading-3.5"
64
- style={{
65
- color: tagColor || 'var(--color-accent, #D49355)',
66
- backgroundColor: tagColor
67
- ? `color-mix(in srgb, ${tagColor} 10%, transparent)`
68
- : 'rgba(var(--color-accent-rgb, 212,147,85), 0.1)',
69
- }}
70
- >
71
- {tag}
72
- </span>
73
- )}
74
-
75
- {/* Label */}
76
- <span className="text-[12px] text-[#FFFFFFCC] leading-4 min-w-0 truncate">{label}</span>
77
-
78
- {/* Spacer if value present */}
79
- {(value || children) && <span className="grow" />}
80
-
81
- {/* Value */}
82
- {value && (
83
- <span className="text-[12px] leading-4 tabular-nums shrink-0" style={{ color: accentColor || '#FFFFFF99' }}>
84
- {value}
85
- </span>
86
- )}
87
-
88
- {/* Custom children (for complex right-side content) */}
89
- {children}
90
- </div>
91
- );
92
- }
93
- );
94
- DataRow.displayName = 'DataRow';
95
-
96
- export type { DataRowProps };
97
- export { DataRow, dataRowVariants };