@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
package/src/components/note.tsx
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
-
import { AlertCircle, AlertTriangle, CheckCircle, Info } from 'lucide-react';
|
|
5
|
-
import * as React from 'react';
|
|
6
|
-
import { cn } from '../utils';
|
|
7
|
-
|
|
8
|
-
const noteVariants = cva('flex items-start gap-2 rounded-md border p-3 text-copy-13', {
|
|
9
|
-
variants: {
|
|
10
|
-
type: {
|
|
11
|
-
default: 'border-gray-alpha-400 bg-gray-alpha-100 text-gray-900',
|
|
12
|
-
error: 'border-red-300 bg-red-100 text-red-900',
|
|
13
|
-
warning: 'border-amber-300 bg-amber-100 text-amber-900',
|
|
14
|
-
success: 'border-green-300 bg-green-100 text-green-900',
|
|
15
|
-
},
|
|
16
|
-
size: {
|
|
17
|
-
default: 'p-3',
|
|
18
|
-
small: 'p-2 text-label-12',
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
defaultVariants: {
|
|
22
|
-
type: 'default',
|
|
23
|
-
size: 'default',
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const iconMap = {
|
|
28
|
-
default: Info,
|
|
29
|
-
error: AlertCircle,
|
|
30
|
-
warning: AlertTriangle,
|
|
31
|
-
success: CheckCircle,
|
|
32
|
-
} as const;
|
|
33
|
-
|
|
34
|
-
interface NoteProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof noteVariants> {
|
|
35
|
-
type?: 'default' | 'error' | 'warning' | 'success';
|
|
36
|
-
label?: boolean | string;
|
|
37
|
-
action?: React.ReactNode;
|
|
38
|
-
disabled?: boolean;
|
|
39
|
-
fill?: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function Note({ className, type = 'default', size, label, action, children, ...props }: NoteProps) {
|
|
43
|
-
const Icon = iconMap[type];
|
|
44
|
-
const showLabel = label !== false;
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<div className={cn(noteVariants({ type, size }), className)} role="alert" {...props}>
|
|
48
|
-
{showLabel && <Icon className="h-4 w-4 shrink-0 mt-0.5" />}
|
|
49
|
-
<div className="flex-1 min-w-0">{children}</div>
|
|
50
|
-
{action && <div className="shrink-0">{action}</div>}
|
|
51
|
-
</div>
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export { Note };
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import NumberFlowBase from '@number-flow/react';
|
|
4
|
-
import type { ComponentProps } from 'react';
|
|
5
|
-
|
|
6
|
-
type NumberFlowProps = ComponentProps<typeof NumberFlowBase>;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* NumberFlow — animated number transitions via @number-flow/react.
|
|
10
|
-
* Thin wrapper that sets KhalOS-branded timing defaults.
|
|
11
|
-
*/
|
|
12
|
-
function NumberFlow({ transformTiming, spinTiming, opacityTiming, ...props }: NumberFlowProps) {
|
|
13
|
-
return (
|
|
14
|
-
<NumberFlowBase
|
|
15
|
-
transformTiming={transformTiming ?? { duration: 800, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)' }}
|
|
16
|
-
spinTiming={spinTiming ?? { duration: 800, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)' }}
|
|
17
|
-
opacityTiming={opacityTiming ?? { duration: 350, easing: 'ease-out' }}
|
|
18
|
-
willChange
|
|
19
|
-
{...props}
|
|
20
|
-
/>
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type { NumberFlowProps };
|
|
25
|
-
export { NumberFlow };
|
|
@@ -1,65 +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
|
-
* PillBadge — LP section-level badge/tag component.
|
|
9
|
-
*
|
|
10
|
-
* Extracted from khal-landing section components:
|
|
11
|
-
* - Capability tags (omnichannel-spotlight.tsx)
|
|
12
|
-
* - Section label pills (fast-secure.tsx header)
|
|
13
|
-
* - Compliance badges (fast-secure.tsx badge strip)
|
|
14
|
-
* - Accent badges (metrics.tsx ROI calculator)
|
|
15
|
-
*
|
|
16
|
-
* Always uppercase with wide tracking — the LP's signature badge style.
|
|
17
|
-
*/
|
|
18
|
-
const pillBadgeVariants = cva(
|
|
19
|
-
'inline-flex items-center gap-1.5 rounded-full font-medium uppercase leading-none whitespace-nowrap',
|
|
20
|
-
{
|
|
21
|
-
variants: {
|
|
22
|
-
variant: {
|
|
23
|
-
/** Subtle border badge — compliance tags, capability tags */
|
|
24
|
-
default: 'border border-[#FFFFFF26] bg-[#FFFFFF08] text-[#FFFFFFCC]',
|
|
25
|
-
/** Muted badge — less prominent */
|
|
26
|
-
muted: 'border border-[#FFFFFF14] bg-[#FFFFFF05] text-[#FFFFFFCC]',
|
|
27
|
-
/** Accent-filled badge — ROI labels, active state badges */
|
|
28
|
-
accent: 'text-[var(--color-accent,#D49355)] bg-[rgba(var(--color-accent-rgb,212,147,85),0.12)]',
|
|
29
|
-
},
|
|
30
|
-
size: {
|
|
31
|
-
sm: 'py-1 px-2.5 text-[10px] tracking-[0.08em]',
|
|
32
|
-
md: 'py-1.5 px-3.5 text-[11px] tracking-widest',
|
|
33
|
-
lg: 'py-2 px-4 text-[11px] tracking-widest',
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
defaultVariants: {
|
|
37
|
-
variant: 'default',
|
|
38
|
-
size: 'md',
|
|
39
|
-
},
|
|
40
|
-
}
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
interface PillBadgeProps extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof pillBadgeVariants> {
|
|
44
|
-
/** Show a small dot indicator before the text */
|
|
45
|
-
dot?: boolean;
|
|
46
|
-
/** Custom dot color (defaults to current text color at 40% opacity) */
|
|
47
|
-
dotColor?: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const PillBadge = React.forwardRef<HTMLSpanElement, PillBadgeProps>(
|
|
51
|
-
({ className, variant, size, dot, dotColor, children, ...props }, ref) => {
|
|
52
|
-
return (
|
|
53
|
-
<span ref={ref} className={cn(pillBadgeVariants({ variant, size }), className)} {...props}>
|
|
54
|
-
{dot && (
|
|
55
|
-
<span className="size-1.5 shrink-0 rounded-full" style={{ backgroundColor: dotColor || '#FFFFFF40' }} />
|
|
56
|
-
)}
|
|
57
|
-
{children}
|
|
58
|
-
</span>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
);
|
|
62
|
-
PillBadge.displayName = 'PillBadge';
|
|
63
|
-
|
|
64
|
-
export type { PillBadgeProps };
|
|
65
|
-
export { PillBadge, pillBadgeVariants };
|
|
@@ -1,70 +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 progressBarVariants = cva(
|
|
8
|
-
'relative w-full overflow-hidden rounded-full [background:var(--khal-border-default)]',
|
|
9
|
-
{
|
|
10
|
-
variants: {
|
|
11
|
-
size: {
|
|
12
|
-
sm: 'h-1.5',
|
|
13
|
-
md: 'h-2.5',
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
defaultVariants: {
|
|
17
|
-
size: 'md',
|
|
18
|
-
},
|
|
19
|
-
}
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
interface ProgressBarProps
|
|
23
|
-
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color'>,
|
|
24
|
-
VariantProps<typeof progressBarVariants> {
|
|
25
|
-
value: number;
|
|
26
|
-
max?: number;
|
|
27
|
-
color?: string;
|
|
28
|
-
showLabel?: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function ProgressBar({
|
|
32
|
-
value,
|
|
33
|
-
max = 100,
|
|
34
|
-
color = 'var(--khal-stage-build)',
|
|
35
|
-
size,
|
|
36
|
-
showLabel = false,
|
|
37
|
-
className,
|
|
38
|
-
...props
|
|
39
|
-
}: ProgressBarProps) {
|
|
40
|
-
const percentage = Math.min(100, Math.max(0, (value / max) * 100));
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<div className={cn('flex items-center gap-2', className)} {...props}>
|
|
44
|
-
<div
|
|
45
|
-
role="progressbar"
|
|
46
|
-
aria-valuenow={value}
|
|
47
|
-
aria-valuemin={0}
|
|
48
|
-
aria-valuemax={max}
|
|
49
|
-
className={progressBarVariants({ size })}
|
|
50
|
-
>
|
|
51
|
-
<div
|
|
52
|
-
className="h-full rounded-full"
|
|
53
|
-
style={{
|
|
54
|
-
width: `${percentage}%`,
|
|
55
|
-
background: `linear-gradient(90deg, color-mix(in srgb, ${color} 85%, black), ${color})`,
|
|
56
|
-
transition: 'width var(--khal-duration-normal) var(--khal-ease-spring)',
|
|
57
|
-
}}
|
|
58
|
-
/>
|
|
59
|
-
</div>
|
|
60
|
-
{showLabel && (
|
|
61
|
-
<span className="shrink-0 text-xs tabular-nums opacity-60">
|
|
62
|
-
{value}/{max}
|
|
63
|
-
</span>
|
|
64
|
-
)}
|
|
65
|
-
</div>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export type { ProgressBarProps };
|
|
70
|
-
export { ProgressBar, progressBarVariants };
|
|
@@ -1,76 +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
|
-
* SectionCard — LP section-level card component.
|
|
9
|
-
*
|
|
10
|
-
* Extracted from khal-landing section components (architecture.tsx, fast-secure.tsx).
|
|
11
|
-
* Two variants matching the LP's two card styles:
|
|
12
|
-
* - `default`: full-border card (architecture section outer cards)
|
|
13
|
-
* - `inset`: top-left border card (architecture mockup panels)
|
|
14
|
-
*/
|
|
15
|
-
const sectionCardVariants = cva('relative flex flex-col overflow-hidden', {
|
|
16
|
-
variants: {
|
|
17
|
-
variant: {
|
|
18
|
-
default: 'rounded-2xl border border-[#FFFFFF1A] bg-[#FFFFFF0A]',
|
|
19
|
-
inset: 'rounded-tl-xl border-t border-l border-[#FFFFFF26] bg-[#111111]',
|
|
20
|
-
solid: 'rounded-2xl border border-[#FFFFFF1A] bg-[#0D0D0D]',
|
|
21
|
-
},
|
|
22
|
-
padding: {
|
|
23
|
-
none: '',
|
|
24
|
-
sm: 'p-4',
|
|
25
|
-
md: 'p-5 sm:p-6',
|
|
26
|
-
lg: 'p-5 sm:p-6 md:p-8',
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
defaultVariants: {
|
|
30
|
-
variant: 'default',
|
|
31
|
-
padding: 'md',
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
interface SectionCardProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof sectionCardVariants> {
|
|
36
|
-
/** Optional gradient overlay color (hex). Renders a subtle top-down gradient. */
|
|
37
|
-
glow?: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const SectionCard = React.forwardRef<HTMLDivElement, SectionCardProps>(
|
|
41
|
-
({ className, variant, padding, glow, children, ...props }, ref) => {
|
|
42
|
-
return (
|
|
43
|
-
<div ref={ref} className={cn(sectionCardVariants({ variant, padding }), className)} {...props}>
|
|
44
|
-
{glow && (
|
|
45
|
-
<div
|
|
46
|
-
className="pointer-events-none absolute inset-0 z-0"
|
|
47
|
-
style={{
|
|
48
|
-
background: `linear-gradient(180deg, ${glow}22 0%, transparent 60%)`,
|
|
49
|
-
}}
|
|
50
|
-
/>
|
|
51
|
-
)}
|
|
52
|
-
<div className="relative z-10 flex flex-col h-full">{children}</div>
|
|
53
|
-
</div>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
);
|
|
57
|
-
SectionCard.displayName = 'SectionCard';
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* SectionCardHeader — optional header row with bottom border separator.
|
|
61
|
-
* Matches the LP mockup panel headers: `py-3 px-4 border-b border-[#FFFFFF1A]`.
|
|
62
|
-
*/
|
|
63
|
-
function SectionCardHeader({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
64
|
-
return (
|
|
65
|
-
<div
|
|
66
|
-
className={cn('flex items-center justify-between py-3 px-4 border-b border-[#FFFFFF1A]', className)}
|
|
67
|
-
{...props}
|
|
68
|
-
>
|
|
69
|
-
{children}
|
|
70
|
-
</div>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
SectionCardHeader.displayName = 'SectionCardHeader';
|
|
74
|
-
|
|
75
|
-
export type { SectionCardProps };
|
|
76
|
-
export { SectionCard, SectionCardHeader, sectionCardVariants };
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
|
4
|
-
import * as React from 'react';
|
|
5
|
-
import { cn } from '../utils';
|
|
6
|
-
|
|
7
|
-
const Separator = React.forwardRef<
|
|
8
|
-
React.ComponentRef<typeof SeparatorPrimitive.Root>,
|
|
9
|
-
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
|
10
|
-
>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
|
|
11
|
-
<SeparatorPrimitive.Root
|
|
12
|
-
ref={ref}
|
|
13
|
-
decorative={decorative}
|
|
14
|
-
orientation={orientation}
|
|
15
|
-
className={cn(
|
|
16
|
-
'shrink-0 bg-gray-alpha-400',
|
|
17
|
-
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
|
18
|
-
className
|
|
19
|
-
)}
|
|
20
|
-
{...props}
|
|
21
|
-
/>
|
|
22
|
-
));
|
|
23
|
-
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
|
24
|
-
|
|
25
|
-
export { Separator };
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
import { cn } from '../utils';
|
|
5
|
-
|
|
6
|
-
const sizeMap = {
|
|
7
|
-
sm: 16,
|
|
8
|
-
md: 20,
|
|
9
|
-
lg: 24,
|
|
10
|
-
} as const;
|
|
11
|
-
|
|
12
|
-
interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
13
|
-
size?: keyof typeof sizeMap;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function Spinner({ size = 'md', className, ...props }: SpinnerProps) {
|
|
17
|
-
const px = sizeMap[size];
|
|
18
|
-
return (
|
|
19
|
-
<div
|
|
20
|
-
role="status"
|
|
21
|
-
aria-label="Loading"
|
|
22
|
-
className={cn('inline-flex items-center justify-center', className)}
|
|
23
|
-
{...props}
|
|
24
|
-
>
|
|
25
|
-
<svg width={px} height={px} viewBox="0 0 20 20" fill="none" className="animate-spin">
|
|
26
|
-
<circle cx="10" cy="10" r="8" stroke="currentColor" strokeWidth="2" strokeLinecap="round" opacity="0.25" />
|
|
27
|
-
<circle
|
|
28
|
-
cx="10"
|
|
29
|
-
cy="10"
|
|
30
|
-
r="8"
|
|
31
|
-
stroke="currentColor"
|
|
32
|
-
strokeWidth="2"
|
|
33
|
-
strokeLinecap="round"
|
|
34
|
-
strokeDasharray="50.26"
|
|
35
|
-
strokeDashoffset="37.7"
|
|
36
|
-
/>
|
|
37
|
-
</svg>
|
|
38
|
-
</div>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export { Spinner };
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
import { cn } from '../utils';
|
|
5
|
-
|
|
6
|
-
const sizeMap = { sm: 8, md: 10, lg: 12 } as const;
|
|
7
|
-
|
|
8
|
-
/** All recognized status states */
|
|
9
|
-
type StatusState = 'live' | 'online' | 'active' | 'working' | 'idle' | 'away' | 'queued' | 'error';
|
|
10
|
-
|
|
11
|
-
const stateConfig: Record<StatusState, { color: string; label: string; pulse: boolean }> = {
|
|
12
|
-
live: { color: '#22c55e', label: 'Live', pulse: true },
|
|
13
|
-
online: { color: '#22c55e', label: 'Online', pulse: false },
|
|
14
|
-
active: { color: '#22c55e', label: 'Active', pulse: true },
|
|
15
|
-
working: { color: '#f59e0b', label: 'Working', pulse: true },
|
|
16
|
-
idle: { color: '#64748b', label: 'Idle', pulse: false },
|
|
17
|
-
away: { color: '#64748b', label: 'Away', pulse: false },
|
|
18
|
-
queued: { color: '#f59e0b', label: 'Queued', pulse: false },
|
|
19
|
-
error: { color: '#ef4444', label: 'Error', pulse: true },
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
interface StatusDotProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
23
|
-
/** Typed state — determines color, pulse, and label automatically */
|
|
24
|
-
state?: StatusState;
|
|
25
|
-
/** Manual color override (legacy support) */
|
|
26
|
-
color?: string;
|
|
27
|
-
/** Manual pulse override */
|
|
28
|
-
pulse?: boolean;
|
|
29
|
-
size?: keyof typeof sizeMap;
|
|
30
|
-
/** Manual label override */
|
|
31
|
-
label?: string;
|
|
32
|
-
/** Show text label next to dot */
|
|
33
|
-
showLabel?: boolean;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function StatusDot({
|
|
37
|
-
state,
|
|
38
|
-
color: colorProp,
|
|
39
|
-
pulse: pulseProp,
|
|
40
|
-
size = 'md',
|
|
41
|
-
label: labelProp,
|
|
42
|
-
showLabel = false,
|
|
43
|
-
className,
|
|
44
|
-
style,
|
|
45
|
-
...props
|
|
46
|
-
}: StatusDotProps) {
|
|
47
|
-
const config = state ? stateConfig[state] : null;
|
|
48
|
-
const color = colorProp ?? config?.color ?? '#64748b';
|
|
49
|
-
const pulse = pulseProp ?? config?.pulse ?? false;
|
|
50
|
-
const label = labelProp ?? config?.label;
|
|
51
|
-
const px = sizeMap[size];
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<span
|
|
55
|
-
role="status"
|
|
56
|
-
aria-label={label}
|
|
57
|
-
className={cn('relative inline-flex shrink-0 items-center gap-1.5', className)}
|
|
58
|
-
style={style}
|
|
59
|
-
{...props}
|
|
60
|
-
>
|
|
61
|
-
<span className="relative inline-flex shrink-0" style={{ width: px, height: px }}>
|
|
62
|
-
{pulse && (
|
|
63
|
-
<span
|
|
64
|
-
className="absolute -inset-0.5 rounded-full"
|
|
65
|
-
style={{
|
|
66
|
-
backgroundColor: color,
|
|
67
|
-
opacity: 0.35,
|
|
68
|
-
animation: 'khal-pulse 2s ease-in-out infinite',
|
|
69
|
-
}}
|
|
70
|
-
/>
|
|
71
|
-
)}
|
|
72
|
-
<span
|
|
73
|
-
className="absolute inset-0 rounded-full"
|
|
74
|
-
style={{
|
|
75
|
-
backgroundColor: color,
|
|
76
|
-
boxShadow: pulse ? `0 0 ${px}px ${color}80` : undefined,
|
|
77
|
-
}}
|
|
78
|
-
/>
|
|
79
|
-
</span>
|
|
80
|
-
{showLabel && label && (
|
|
81
|
-
<span className="text-[11px] leading-none" style={{ color }}>
|
|
82
|
-
{label}
|
|
83
|
-
</span>
|
|
84
|
-
)}
|
|
85
|
-
</span>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export type { StatusDotProps, StatusState };
|
|
90
|
-
export { StatusDot, stateConfig };
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import * as SwitchPrimitive from '@radix-ui/react-switch';
|
|
4
|
-
import * as React from 'react';
|
|
5
|
-
import { cn } from '../utils';
|
|
6
|
-
|
|
7
|
-
interface ToggleProps extends Omit<React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>, 'onChange'> {
|
|
8
|
-
onChange?: (checked: boolean) => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const Toggle = React.forwardRef<React.ComponentRef<typeof SwitchPrimitive.Root>, ToggleProps>(
|
|
12
|
-
({ className, onChange, onCheckedChange, ...props }, ref) => (
|
|
13
|
-
<SwitchPrimitive.Root
|
|
14
|
-
className={cn(
|
|
15
|
-
'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors',
|
|
16
|
-
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-700 focus-visible:ring-offset-2 focus-visible:ring-offset-background-100',
|
|
17
|
-
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
18
|
-
'data-[state=checked]:bg-gray-1000 data-[state=unchecked]:bg-gray-alpha-400',
|
|
19
|
-
className
|
|
20
|
-
)}
|
|
21
|
-
onCheckedChange={onCheckedChange ?? onChange}
|
|
22
|
-
{...props}
|
|
23
|
-
ref={ref}
|
|
24
|
-
>
|
|
25
|
-
<SwitchPrimitive.Thumb
|
|
26
|
-
className={cn(
|
|
27
|
-
'pointer-events-none block h-4 w-4 rounded-full bg-white shadow-lg ring-0 transition-transform',
|
|
28
|
-
'data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0'
|
|
29
|
-
)}
|
|
30
|
-
/>
|
|
31
|
-
</SwitchPrimitive.Root>
|
|
32
|
-
)
|
|
33
|
-
);
|
|
34
|
-
Toggle.displayName = 'Toggle';
|
|
35
|
-
|
|
36
|
-
export { Toggle };
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { ThemeProvider as NextThemesProvider } from 'next-themes';
|
|
4
|
-
import { useEffect } from 'react';
|
|
5
|
-
import { useThemeStore } from '../stores/theme-store';
|
|
6
|
-
|
|
7
|
-
function ReduceMotionSync() {
|
|
8
|
-
const reduceMotion = useThemeStore((s) => s.reduceMotion);
|
|
9
|
-
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
document.documentElement.setAttribute('data-reduce-motion', String(reduceMotion));
|
|
12
|
-
}, [reduceMotion]);
|
|
13
|
-
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function GlassSync() {
|
|
18
|
-
const glassEnabled = useThemeStore((s) => s.glassEnabled);
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
const el = document.documentElement;
|
|
22
|
-
if (glassEnabled) {
|
|
23
|
-
el.setAttribute('data-glass', '');
|
|
24
|
-
el.style.setProperty('--khal-glass-enabled', '1');
|
|
25
|
-
} else {
|
|
26
|
-
el.removeAttribute('data-glass');
|
|
27
|
-
el.style.setProperty('--khal-glass-enabled', '0');
|
|
28
|
-
}
|
|
29
|
-
}, [glassEnabled]);
|
|
30
|
-
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function GpuTerminalsSync() {
|
|
35
|
-
const gpuTerminals = useThemeStore((s) => s.gpuTerminals);
|
|
36
|
-
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
// Sync to the localStorage key the terminal reads on mount
|
|
39
|
-
if (gpuTerminals) {
|
|
40
|
-
localStorage.setItem('khal-gpu-terminals', 'true');
|
|
41
|
-
} else {
|
|
42
|
-
localStorage.removeItem('khal-gpu-terminals');
|
|
43
|
-
}
|
|
44
|
-
}, [gpuTerminals]);
|
|
45
|
-
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function ThemeProvider({ children, ...props }: React.ComponentProps<typeof NextThemesProvider>) {
|
|
50
|
-
return (
|
|
51
|
-
<NextThemesProvider {...props}>
|
|
52
|
-
<ReduceMotionSync />
|
|
53
|
-
<GlassSync />
|
|
54
|
-
<GpuTerminalsSync />
|
|
55
|
-
{children}
|
|
56
|
-
</NextThemesProvider>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Monitor, Moon, Sun } from 'lucide-react';
|
|
4
|
-
import { useTheme } from 'next-themes';
|
|
5
|
-
import { useEffect, useState } from 'react';
|
|
6
|
-
import { cn } from '../utils';
|
|
7
|
-
|
|
8
|
-
const themes = [
|
|
9
|
-
{ value: 'system', label: 'System', icon: Monitor },
|
|
10
|
-
{ value: 'light', label: 'Light', icon: Sun },
|
|
11
|
-
{ value: 'dark', label: 'Dark', icon: Moon },
|
|
12
|
-
] as const;
|
|
13
|
-
|
|
14
|
-
interface ThemeSwitcherProps {
|
|
15
|
-
small?: boolean;
|
|
16
|
-
className?: string;
|
|
17
|
-
onThemeSwitch?: (theme: string) => void;
|
|
18
|
-
disabled?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function ThemeSwitcher({ small, className, onThemeSwitch, disabled }: ThemeSwitcherProps) {
|
|
22
|
-
const { theme, setTheme } = useTheme();
|
|
23
|
-
const [mounted, setMounted] = useState(false);
|
|
24
|
-
useEffect(() => setMounted(true), []);
|
|
25
|
-
|
|
26
|
-
// Avoid hydration mismatch: useTheme() returns different values on server vs client
|
|
27
|
-
const resolvedTheme = mounted ? theme : undefined;
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<fieldset
|
|
31
|
-
className={cn('inline-flex items-center gap-1 rounded-lg bg-gray-alpha-100 p-1', className)}
|
|
32
|
-
disabled={disabled}
|
|
33
|
-
>
|
|
34
|
-
{themes.map(({ value, label, icon: Icon }) => (
|
|
35
|
-
<button
|
|
36
|
-
key={value}
|
|
37
|
-
type="button"
|
|
38
|
-
role="radio"
|
|
39
|
-
aria-checked={resolvedTheme === value}
|
|
40
|
-
onClick={() => {
|
|
41
|
-
setTheme(value);
|
|
42
|
-
onThemeSwitch?.(value);
|
|
43
|
-
}}
|
|
44
|
-
className={cn(
|
|
45
|
-
'inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-label-12 transition-colors cursor-pointer',
|
|
46
|
-
resolvedTheme === value
|
|
47
|
-
? 'bg-background-100 text-gray-1000 shadow-sm'
|
|
48
|
-
: 'text-gray-700 hover:text-gray-1000'
|
|
49
|
-
)}
|
|
50
|
-
>
|
|
51
|
-
<Icon className={small ? 'h-3.5 w-3.5' : 'h-4 w-4'} />
|
|
52
|
-
{!small && label}
|
|
53
|
-
</button>
|
|
54
|
-
))}
|
|
55
|
-
</fieldset>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export { ThemeSwitcher };
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import type { ReactNode } from 'react';
|
|
4
|
-
import { cn } from '../utils';
|
|
5
|
-
|
|
6
|
-
interface TickerBarProps {
|
|
7
|
-
/** Items to scroll — rendered twice for seamless looping */
|
|
8
|
-
children: ReactNode;
|
|
9
|
-
/** Animation duration in seconds (default 30) */
|
|
10
|
-
duration?: number;
|
|
11
|
-
/** Pause on hover */
|
|
12
|
-
pauseOnHover?: boolean;
|
|
13
|
-
className?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function TickerBar({ children, duration = 30, pauseOnHover = true, className }: TickerBarProps) {
|
|
17
|
-
return (
|
|
18
|
-
<div
|
|
19
|
-
className={cn('relative w-full overflow-hidden', className)}
|
|
20
|
-
style={{
|
|
21
|
-
maskImage: 'linear-gradient(to right, transparent, black 10%, black 90%, transparent)',
|
|
22
|
-
WebkitMaskImage: 'linear-gradient(to right, transparent, black 10%, black 90%, transparent)',
|
|
23
|
-
}}
|
|
24
|
-
>
|
|
25
|
-
<div
|
|
26
|
-
className={cn('flex w-max', pauseOnHover && 'hover:[animation-play-state:paused]')}
|
|
27
|
-
style={{
|
|
28
|
-
animation: `khal-ticker ${duration}s linear infinite`,
|
|
29
|
-
}}
|
|
30
|
-
>
|
|
31
|
-
<div className="flex shrink-0 items-center">{children}</div>
|
|
32
|
-
<div className="flex shrink-0 items-center" aria-hidden>
|
|
33
|
-
{children}
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type { TickerBarProps };
|
|
41
|
-
export { TickerBar };
|