@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.
- package/LICENSE +94 -0
- package/README.md +25 -0
- package/dist/index.cjs +2661 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +926 -0
- package/dist/index.d.ts +926 -0
- package/dist/index.js +2510 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -40
- package/tokens.css +260 -238
- package/src/components/ContextMenu.tsx +0 -130
- package/src/components/avatar.tsx +0 -71
- package/src/components/badge.tsx +0 -39
- package/src/components/button.tsx +0 -102
- package/src/components/command.tsx +0 -165
- package/src/components/cost-counter.tsx +0 -75
- package/src/components/data-row.tsx +0 -97
- package/src/components/dropdown-menu.tsx +0 -233
- package/src/components/glass-card.tsx +0 -74
- package/src/components/input.tsx +0 -48
- package/src/components/khal-logo.tsx +0 -73
- package/src/components/live-feed.tsx +0 -109
- package/src/components/mesh-gradient.tsx +0 -57
- package/src/components/metric-display.tsx +0 -93
- package/src/components/note.tsx +0 -55
- package/src/components/number-flow.tsx +0 -25
- package/src/components/pill-badge.tsx +0 -65
- package/src/components/progress-bar.tsx +0 -70
- package/src/components/section-card.tsx +0 -76
- package/src/components/separator.tsx +0 -25
- package/src/components/spinner.tsx +0 -42
- package/src/components/status-dot.tsx +0 -90
- package/src/components/switch.tsx +0 -36
- package/src/components/theme-provider.tsx +0 -58
- package/src/components/theme-switcher.tsx +0 -59
- package/src/components/ticker-bar.tsx +0 -41
- package/src/components/tooltip.tsx +0 -62
- package/src/components/window-minimized-context.tsx +0 -29
- package/src/hooks/useReducedMotion.ts +0 -21
- package/src/index.ts +0 -58
- package/src/lib/animations.ts +0 -50
- package/src/primitives/collapsible-sidebar.tsx +0 -226
- package/src/primitives/dialog.tsx +0 -76
- package/src/primitives/empty-state.tsx +0 -43
- package/src/primitives/index.ts +0 -22
- package/src/primitives/list-view.tsx +0 -155
- package/src/primitives/property-panel.tsx +0 -108
- package/src/primitives/section-header.tsx +0 -19
- package/src/primitives/sidebar-nav.tsx +0 -110
- package/src/primitives/split-pane.tsx +0 -146
- package/src/primitives/status-badge.tsx +0 -10
- package/src/primitives/status-bar.tsx +0 -100
- package/src/primitives/toolbar.tsx +0 -152
- package/src/server.ts +0 -4
- package/src/stores/notification-store.ts +0 -271
- package/src/stores/theme-store.ts +0 -33
- package/src/tokens/lp-tokens.ts +0 -36
- package/src/utils.ts +0 -6
- 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 };
|
package/src/components/badge.tsx
DELETED
|
@@ -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 };
|