@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,152 +0,0 @@
1
- 'use client';
2
-
3
- import { type ButtonHTMLAttributes, forwardRef, type ReactNode } from 'react';
4
- import { Separator } from '../components/separator';
5
- import { Tooltip } from '../components/tooltip';
6
-
7
- // ---------------------------------------------------------------------------
8
- // Toolbar — horizontal bar of icon buttons, groups, and separators.
9
- //
10
- // Usage:
11
- // <Toolbar>
12
- // <Toolbar.Group>
13
- // <Toolbar.Button tooltip="Back" onClick={goBack}><ChevronLeft /></Toolbar.Button>
14
- // <Toolbar.Button tooltip="Forward" onClick={goFwd}><ChevronRight /></Toolbar.Button>
15
- // </Toolbar.Group>
16
- // <Toolbar.Separator />
17
- // <Toolbar.Group>
18
- // <Toolbar.Button tooltip="Refresh" active={loading}><Refresh /></Toolbar.Button>
19
- // </Toolbar.Group>
20
- // <Toolbar.Spacer />
21
- // <Toolbar.Text>3 items</Toolbar.Text>
22
- // </Toolbar>
23
- // ---------------------------------------------------------------------------
24
-
25
- interface ToolbarProps {
26
- children: ReactNode;
27
- className?: string;
28
- }
29
-
30
- function ToolbarRoot({ children, className = '' }: ToolbarProps) {
31
- return (
32
- <div
33
- className={`flex h-9 shrink-0 items-center gap-0.5 border-b border-gray-alpha-200 bg-background-100 px-1.5 ${className}`}
34
- role="toolbar"
35
- >
36
- {children}
37
- </div>
38
- );
39
- }
40
-
41
- // ---------------------------------------------------------------------------
42
- // Toolbar.Button
43
- // ---------------------------------------------------------------------------
44
-
45
- interface ToolbarButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
46
- tooltip?: string;
47
- active?: boolean;
48
- children: ReactNode;
49
- }
50
-
51
- const ToolbarButton = forwardRef<HTMLButtonElement, ToolbarButtonProps>(function ToolbarButton(
52
- { tooltip, active, children, className = '', disabled, ...props },
53
- ref
54
- ) {
55
- const btn = (
56
- <button
57
- ref={ref}
58
- disabled={disabled}
59
- className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-900 transition-colors [&>svg]:h-3.5 [&>svg]:w-3.5
60
- hover:bg-gray-alpha-200 hover:text-gray-1000
61
- disabled:pointer-events-none disabled:opacity-40
62
- ${active ? 'bg-gray-alpha-200 text-gray-1000' : ''}
63
- ${className}`}
64
- {...props}
65
- >
66
- {children}
67
- </button>
68
- );
69
-
70
- if (tooltip) {
71
- return (
72
- <Tooltip text={tooltip} desktopOnly>
73
- {btn}
74
- </Tooltip>
75
- );
76
- }
77
- return btn;
78
- });
79
-
80
- // ---------------------------------------------------------------------------
81
- // Toolbar.Group — groups buttons together with tighter spacing
82
- // ---------------------------------------------------------------------------
83
-
84
- function ToolbarGroup({ children, className = '' }: { children: ReactNode; className?: string }) {
85
- return <div className={`flex items-center gap-px ${className}`}>{children}</div>;
86
- }
87
-
88
- // ---------------------------------------------------------------------------
89
- // Toolbar.Separator
90
- // ---------------------------------------------------------------------------
91
-
92
- function ToolbarSeparator() {
93
- return <Separator orientation="vertical" className="mx-1 h-4" />;
94
- }
95
-
96
- // ---------------------------------------------------------------------------
97
- // Toolbar.Spacer — pushes subsequent items to the right
98
- // ---------------------------------------------------------------------------
99
-
100
- function ToolbarSpacer() {
101
- return <div className="flex-1" />;
102
- }
103
-
104
- // ---------------------------------------------------------------------------
105
- // Toolbar.Text — inline text label
106
- // ---------------------------------------------------------------------------
107
-
108
- function ToolbarText({ children, className = '' }: { children: ReactNode; className?: string }) {
109
- return <span className={`px-1.5 text-label-13 text-gray-900 ${className}`}>{children}</span>;
110
- }
111
-
112
- // ---------------------------------------------------------------------------
113
- // Toolbar.Input — inline input (e.g. address bar)
114
- // ---------------------------------------------------------------------------
115
-
116
- function ToolbarInput({
117
- value,
118
- onChange,
119
- placeholder,
120
- className = '',
121
- readOnly,
122
- }: {
123
- value?: string;
124
- onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
125
- placeholder?: string;
126
- className?: string;
127
- readOnly?: boolean;
128
- }) {
129
- return (
130
- <input
131
- type="text"
132
- value={value}
133
- onChange={onChange}
134
- placeholder={placeholder}
135
- readOnly={readOnly}
136
- className={`h-6 flex-1 rounded-md border border-gray-alpha-200 bg-gray-alpha-100 px-2 font-mono text-label-13 text-gray-1000 placeholder:text-gray-700 outline-none transition-colors focus:border-blue-700 ${className}`}
137
- />
138
- );
139
- }
140
-
141
- // ---------------------------------------------------------------------------
142
- // Export as compound component
143
- // ---------------------------------------------------------------------------
144
-
145
- export const Toolbar = Object.assign(ToolbarRoot, {
146
- Button: ToolbarButton,
147
- Group: ToolbarGroup,
148
- Separator: ToolbarSeparator,
149
- Spacer: ToolbarSpacer,
150
- Text: ToolbarText,
151
- Input: ToolbarInput,
152
- });
package/src/server.ts DELETED
@@ -1,4 +0,0 @@
1
- // Server-safe exports for API routes and background services.
2
- // Keep this entrypoint free of client hooks/components.
3
-
4
- export { SUBJECTS } from '@khal-os/sdk/app';
@@ -1,271 +0,0 @@
1
- import { create } from 'zustand';
2
-
3
- export type NotificationUrgency = 'low' | 'normal' | 'critical';
4
- export type DesktopNotifMode = 'background' | 'always' | 'off';
5
-
6
- export interface DesktopNotification {
7
- id: number;
8
- replacesId: number;
9
- appName: string;
10
- summary: string;
11
- body: string;
12
- icon: string | null;
13
- actions: string[];
14
- expires: number;
15
- timestamp: number;
16
- read: boolean;
17
- urgency: NotificationUrgency;
18
- category: string | null;
19
- transient: boolean;
20
- workspaceId: string | null;
21
- }
22
-
23
- export interface TrayIcon {
24
- wid: number;
25
- title: string;
26
- icon: string | null;
27
- }
28
-
29
- interface NotificationStore {
30
- notifications: DesktopNotification[];
31
- history: DesktopNotification[];
32
- trayIcons: Map<number, TrayIcon>;
33
- centerOpen: boolean;
34
- unreadCount: number;
35
-
36
- // Preferences
37
- doNotDisturb: boolean;
38
- desktopNotifMode: DesktopNotifMode;
39
- browserPermission: NotificationPermission;
40
-
41
- setDoNotDisturb: (value: boolean) => void;
42
- setDesktopNotifMode: (mode: DesktopNotifMode) => void;
43
- requestBrowserPermission: () => Promise<NotificationPermission>;
44
- syncBrowserPermission: () => void;
45
-
46
- addNotification: (
47
- notification: Omit<
48
- DesktopNotification,
49
- 'timestamp' | 'read' | 'appName' | 'urgency' | 'category' | 'transient' | 'workspaceId'
50
- > &
51
- Partial<Pick<DesktopNotification, 'appName' | 'urgency' | 'category' | 'transient' | 'workspaceId'>>
52
- ) => void;
53
- dismissNotification: (id: number) => void;
54
- hideNotification: (id: number) => void;
55
- clearHistory: () => void;
56
- markAllRead: () => void;
57
- toggleCenter: () => void;
58
- closeCenter: () => void;
59
-
60
- addTrayIcon: (icon: TrayIcon) => void;
61
- removeTrayIcon: (wid: number) => void;
62
- updateTrayIcon: (wid: number, updates: Partial<TrayIcon>) => void;
63
- }
64
-
65
- const MAX_VISIBLE = 5;
66
- const MAX_HISTORY = 50;
67
-
68
- const PREFS_KEY = 'khal_os_notification_prefs';
69
-
70
- function loadPrefs(): {
71
- doNotDisturb: boolean;
72
- desktopNotifMode: DesktopNotifMode;
73
- } {
74
- if (typeof window === 'undefined') return { doNotDisturb: false, desktopNotifMode: 'background' };
75
- try {
76
- const raw = localStorage.getItem(PREFS_KEY);
77
- if (raw) return JSON.parse(raw);
78
- } catch {}
79
- return { doNotDisturb: false, desktopNotifMode: 'background' };
80
- }
81
-
82
- function savePrefs(prefs: { doNotDisturb: boolean; desktopNotifMode: DesktopNotifMode }) {
83
- try {
84
- localStorage.setItem(PREFS_KEY, JSON.stringify(prefs));
85
- } catch {}
86
- }
87
-
88
- function getBrowserPermission(): NotificationPermission {
89
- if (typeof window === 'undefined' || !('Notification' in window)) return 'denied';
90
- return Notification.permission;
91
- }
92
-
93
- function sendBrowserNotification(notif: DesktopNotification) {
94
- if (typeof window === 'undefined' || !('Notification' in window) || Notification.permission !== 'granted') return;
95
-
96
- const n = new Notification(notif.summary, {
97
- body: notif.body || undefined,
98
- icon: notif.icon || undefined,
99
- tag: String(notif.id),
100
- silent: notif.urgency === 'low',
101
- });
102
-
103
- n.onclick = () => {
104
- window.focus();
105
- n.close();
106
- };
107
-
108
- if (notif.expires > 0) {
109
- setTimeout(() => n.close(), notif.expires);
110
- } else if (notif.urgency !== 'critical') {
111
- setTimeout(() => n.close(), 6000);
112
- }
113
- }
114
-
115
- const initialPrefs = loadPrefs();
116
-
117
- export const useNotificationStore = create<NotificationStore>()((set, get) => ({
118
- notifications: [],
119
- history: [],
120
- trayIcons: new Map(),
121
- centerOpen: false,
122
- unreadCount: 0,
123
-
124
- doNotDisturb: initialPrefs.doNotDisturb,
125
- desktopNotifMode: initialPrefs.desktopNotifMode,
126
- browserPermission: getBrowserPermission(),
127
-
128
- setDoNotDisturb: (value) => {
129
- set({ doNotDisturb: value });
130
- savePrefs({ doNotDisturb: value, desktopNotifMode: get().desktopNotifMode });
131
- },
132
-
133
- setDesktopNotifMode: (mode) => {
134
- set({ desktopNotifMode: mode });
135
- savePrefs({ doNotDisturb: get().doNotDisturb, desktopNotifMode: mode });
136
- },
137
-
138
- requestBrowserPermission: async () => {
139
- if (typeof window === 'undefined' || !('Notification' in window)) return 'denied';
140
- const result = await Notification.requestPermission();
141
- set({ browserPermission: result });
142
- return result;
143
- },
144
-
145
- syncBrowserPermission: () => {
146
- set({ browserPermission: getBrowserPermission() });
147
- },
148
-
149
- addNotification: (notification) => {
150
- const full: DesktopNotification = {
151
- ...notification,
152
- appName: notification.appName ?? '',
153
- urgency: notification.urgency ?? 'normal',
154
- category: notification.category ?? null,
155
- transient: notification.transient ?? false,
156
- workspaceId: notification.workspaceId ?? null,
157
- timestamp: Date.now(),
158
- read: false,
159
- };
160
-
161
- const { doNotDisturb, desktopNotifMode, browserPermission } = get();
162
-
163
- // Always add to history regardless of DND
164
- set((state) => {
165
- const history = full.transient ? state.history : [full, ...state.history].slice(0, MAX_HISTORY);
166
-
167
- // In DND mode, skip visible toasts (still record in history)
168
- if (doNotDisturb && full.urgency !== 'critical') {
169
- return {
170
- history,
171
- unreadCount: full.transient ? state.unreadCount : state.unreadCount + 1,
172
- };
173
- }
174
-
175
- let notifications = [...state.notifications];
176
-
177
- if (notification.replacesId) {
178
- notifications = notifications.filter((n) => n.id !== notification.replacesId);
179
- }
180
-
181
- notifications = [full, ...notifications].slice(0, MAX_VISIBLE);
182
-
183
- return {
184
- notifications,
185
- history,
186
- unreadCount: full.transient ? state.unreadCount : state.unreadCount + 1,
187
- };
188
- });
189
-
190
- // Bridge to browser desktop notifications
191
- if (browserPermission === 'granted' && desktopNotifMode !== 'off' && !full.transient) {
192
- const shouldSend = desktopNotifMode === 'always' || (desktopNotifMode === 'background' && document.hidden);
193
- if (shouldSend) {
194
- sendBrowserNotification(full);
195
- }
196
- }
197
-
198
- const isCritical = full.urgency === 'critical';
199
- if (full.expires > 0) {
200
- setTimeout(() => {
201
- get().hideNotification(full.id);
202
- }, full.expires);
203
- } else if (!isCritical) {
204
- setTimeout(() => {
205
- get().hideNotification(full.id);
206
- }, 6000);
207
- }
208
- },
209
-
210
- dismissNotification: (id) => {
211
- set((state) => ({
212
- notifications: state.notifications.filter((n) => n.id !== id),
213
- }));
214
- },
215
-
216
- hideNotification: (id) => {
217
- set((state) => ({
218
- notifications: state.notifications.filter((n) => n.id !== id),
219
- }));
220
- },
221
-
222
- clearHistory: () => {
223
- set({ history: [], unreadCount: 0 });
224
- },
225
-
226
- markAllRead: () => {
227
- set((state) => ({
228
- history: state.history.map((n) => ({ ...n, read: true })),
229
- unreadCount: 0,
230
- }));
231
- },
232
-
233
- toggleCenter: () => {
234
- const opening = !get().centerOpen;
235
- set({ centerOpen: opening });
236
- if (opening) {
237
- get().markAllRead();
238
- }
239
- },
240
-
241
- closeCenter: () => {
242
- set({ centerOpen: false });
243
- },
244
-
245
- addTrayIcon: (icon) => {
246
- set((state) => {
247
- const next = new Map(state.trayIcons);
248
- next.set(icon.wid, icon);
249
- return { trayIcons: next };
250
- });
251
- },
252
-
253
- removeTrayIcon: (wid) => {
254
- set((state) => {
255
- const next = new Map(state.trayIcons);
256
- next.delete(wid);
257
- return { trayIcons: next };
258
- });
259
- },
260
-
261
- updateTrayIcon: (wid, updates) => {
262
- set((state) => {
263
- const next = new Map(state.trayIcons);
264
- const existing = next.get(wid);
265
- if (existing) {
266
- next.set(wid, { ...existing, ...updates });
267
- }
268
- return { trayIcons: next };
269
- });
270
- },
271
- }));
@@ -1,33 +0,0 @@
1
- import { create } from 'zustand';
2
- import { persist } from 'zustand/middleware';
3
-
4
- type ThemeMode = 'light' | 'dark' | 'system';
5
-
6
- interface ThemeStore {
7
- mode: ThemeMode;
8
- setMode: (mode: ThemeMode) => void;
9
- reduceMotion: boolean;
10
- setReduceMotion: (value: boolean) => void;
11
- glassEnabled: boolean;
12
- setGlassEnabled: (value: boolean) => void;
13
- gpuTerminals: boolean;
14
- setGpuTerminals: (value: boolean) => void;
15
- }
16
-
17
- export const useThemeStore = create<ThemeStore>()(
18
- persist(
19
- (set) => ({
20
- mode: 'dark' as ThemeMode,
21
- setMode: (mode) => set({ mode }),
22
- reduceMotion: false,
23
- setReduceMotion: (reduceMotion) => set({ reduceMotion }),
24
- glassEnabled: false,
25
- setGlassEnabled: (glassEnabled) => set({ glassEnabled }),
26
- gpuTerminals: false,
27
- setGpuTerminals: (gpuTerminals) => set({ gpuTerminals }),
28
- }),
29
- {
30
- name: 'khal-theme',
31
- }
32
- )
33
- );
@@ -1,36 +0,0 @@
1
- /**
2
- * LP Design Tokens — extracted from khal-landing hero-os-showcase.tsx
3
- * These are the canonical reference colors from the landing page.
4
- * The OS theme CSS variables consume these values.
5
- */
6
-
7
- // Surface colors
8
- export const WIN_BG = '#111318';
9
- export const CHROME_BG = '#0D0F14';
10
- export const CELL_BG = '#0D1017';
11
- export const WIN_BORDER = '#1E2330';
12
- export const WIN_BORDER_FOCUSED = '#333D55';
13
-
14
- // Text hierarchy
15
- export const TEXT_PRIMARY = '#E8EAF0';
16
- export const TEXT_SECONDARY = '#8B92A5';
17
- export const TEXT_TERTIARY = '#555D73';
18
-
19
- // Accent
20
- export const ACCENT_BLUE = '#0A6FE0';
21
-
22
- // Mesh gradient palette (8-color navy-to-gold)
23
- export const MESH_GRADIENT_PALETTE = [
24
- '#030508',
25
- '#070D15',
26
- '#0C1A2E',
27
- '#1A4A7A',
28
- '#2A3040',
29
- '#5C4A38',
30
- '#8B6B42',
31
- '#D49355',
32
- ] as const;
33
-
34
- // Radii
35
- export const WINDOW_RADIUS = '12px';
36
- export const BUTTON_RADIUS = '10px';
package/src/utils.ts DELETED
@@ -1,6 +0,0 @@
1
- import { type ClassValue, clsx } from 'clsx';
2
- import { twMerge } from 'tailwind-merge';
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs));
6
- }
package/tsconfig.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "lib": ["dom", "dom.iterable", "esnext"],
5
- "strict": true,
6
- "noEmit": true,
7
- "esModuleInterop": true,
8
- "module": "esnext",
9
- "moduleResolution": "bundler",
10
- "jsx": "react-jsx",
11
- "incremental": true,
12
- "paths": {
13
- "@/*": ["../../src/*"]
14
- }
15
- },
16
- "include": ["src/**/*.ts", "src/**/*.tsx"]
17
- }