@moldable-ai/ui 0.1.1

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 (117) hide show
  1. package/LICENSE +101 -0
  2. package/dist/components/chat/chat-input.d.ts +14 -0
  3. package/dist/components/chat/chat-input.d.ts.map +1 -0
  4. package/dist/components/chat/chat-input.js +36 -0
  5. package/dist/components/chat/chat-message.d.ts +30 -0
  6. package/dist/components/chat/chat-message.d.ts.map +1 -0
  7. package/dist/components/chat/chat-message.js +250 -0
  8. package/dist/components/chat/chat-messages.d.ts +10 -0
  9. package/dist/components/chat/chat-messages.d.ts.map +1 -0
  10. package/dist/components/chat/chat-messages.js +30 -0
  11. package/dist/components/chat/chat-panel.d.ts +60 -0
  12. package/dist/components/chat/chat-panel.d.ts.map +1 -0
  13. package/dist/components/chat/chat-panel.js +115 -0
  14. package/dist/components/chat/conversation-history.d.ts +18 -0
  15. package/dist/components/chat/conversation-history.d.ts.map +1 -0
  16. package/dist/components/chat/conversation-history.js +32 -0
  17. package/dist/components/chat/index.d.ts +10 -0
  18. package/dist/components/chat/index.d.ts.map +1 -0
  19. package/dist/components/chat/index.js +9 -0
  20. package/dist/components/chat/model-selector.d.ts +16 -0
  21. package/dist/components/chat/model-selector.d.ts.map +1 -0
  22. package/dist/components/chat/model-selector.js +9 -0
  23. package/dist/components/chat/reasoning-effort-selector.d.ts +14 -0
  24. package/dist/components/chat/reasoning-effort-selector.d.ts.map +1 -0
  25. package/dist/components/chat/reasoning-effort-selector.js +10 -0
  26. package/dist/components/chat/thinking-timeline.d.ts +21 -0
  27. package/dist/components/chat/thinking-timeline.d.ts.map +1 -0
  28. package/dist/components/chat/thinking-timeline.js +46 -0
  29. package/dist/components/chat/tool-handlers.d.ts +21 -0
  30. package/dist/components/chat/tool-handlers.d.ts.map +1 -0
  31. package/dist/components/chat/tool-handlers.js +434 -0
  32. package/dist/components/code-block.d.ts +9 -0
  33. package/dist/components/code-block.d.ts.map +1 -0
  34. package/dist/components/code-block.js +112 -0
  35. package/dist/components/markdown.d.ts +10 -0
  36. package/dist/components/markdown.d.ts.map +1 -0
  37. package/dist/components/markdown.js +189 -0
  38. package/dist/components/ui/alert-dialog.d.ts +15 -0
  39. package/dist/components/ui/alert-dialog.d.ts.map +1 -0
  40. package/dist/components/ui/alert-dialog.js +38 -0
  41. package/dist/components/ui/badge.d.ts +10 -0
  42. package/dist/components/ui/badge.d.ts.map +1 -0
  43. package/dist/components/ui/badge.js +22 -0
  44. package/dist/components/ui/button.d.ts +12 -0
  45. package/dist/components/ui/button.d.ts.map +1 -0
  46. package/dist/components/ui/button.js +35 -0
  47. package/dist/components/ui/card.d.ts +9 -0
  48. package/dist/components/ui/card.d.ts.map +1 -0
  49. package/dist/components/ui/card.js +16 -0
  50. package/dist/components/ui/checkbox.d.ts +5 -0
  51. package/dist/components/ui/checkbox.d.ts.map +1 -0
  52. package/dist/components/ui/checkbox.js +9 -0
  53. package/dist/components/ui/collapsible.d.ts +6 -0
  54. package/dist/components/ui/collapsible.d.ts.map +1 -0
  55. package/dist/components/ui/collapsible.js +5 -0
  56. package/dist/components/ui/command.d.ts +19 -0
  57. package/dist/components/ui/command.d.ts.map +1 -0
  58. package/dist/components/ui/command.js +29 -0
  59. package/dist/components/ui/dialog.d.ts +20 -0
  60. package/dist/components/ui/dialog.d.ts.map +1 -0
  61. package/dist/components/ui/dialog.js +23 -0
  62. package/dist/components/ui/dropdown-menu.d.ts +26 -0
  63. package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
  64. package/dist/components/ui/dropdown-menu.js +51 -0
  65. package/dist/components/ui/index.d.ts +19 -0
  66. package/dist/components/ui/index.d.ts.map +1 -0
  67. package/dist/components/ui/index.js +18 -0
  68. package/dist/components/ui/input.d.ts +5 -0
  69. package/dist/components/ui/input.d.ts.map +1 -0
  70. package/dist/components/ui/input.js +8 -0
  71. package/dist/components/ui/label.d.ts +5 -0
  72. package/dist/components/ui/label.d.ts.map +1 -0
  73. package/dist/components/ui/label.js +8 -0
  74. package/dist/components/ui/popover.d.ts +8 -0
  75. package/dist/components/ui/popover.d.ts.map +1 -0
  76. package/dist/components/ui/popover.js +16 -0
  77. package/dist/components/ui/scroll-area.d.ts +6 -0
  78. package/dist/components/ui/scroll-area.d.ts.map +1 -0
  79. package/dist/components/ui/scroll-area.js +11 -0
  80. package/dist/components/ui/select.d.ts +14 -0
  81. package/dist/components/ui/select.d.ts.map +1 -0
  82. package/dist/components/ui/select.js +26 -0
  83. package/dist/components/ui/switch.d.ts +5 -0
  84. package/dist/components/ui/switch.d.ts.map +1 -0
  85. package/dist/components/ui/switch.js +8 -0
  86. package/dist/components/ui/tabs.d.ts +8 -0
  87. package/dist/components/ui/tabs.d.ts.map +1 -0
  88. package/dist/components/ui/tabs.js +16 -0
  89. package/dist/components/ui/textarea.d.ts +5 -0
  90. package/dist/components/ui/textarea.d.ts.map +1 -0
  91. package/dist/components/ui/textarea.js +8 -0
  92. package/dist/components/ui/tooltip.d.ts +8 -0
  93. package/dist/components/ui/tooltip.d.ts.map +1 -0
  94. package/dist/components/ui/tooltip.js +17 -0
  95. package/dist/components/widget-layout.d.ts +10 -0
  96. package/dist/components/widget-layout.d.ts.map +1 -0
  97. package/dist/components/widget-layout.js +30 -0
  98. package/dist/index.d.ts +10 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +18 -0
  101. package/dist/lib/commands.d.ts +74 -0
  102. package/dist/lib/commands.d.ts.map +1 -0
  103. package/dist/lib/commands.js +73 -0
  104. package/dist/lib/commands.test.d.ts +2 -0
  105. package/dist/lib/commands.test.d.ts.map +1 -0
  106. package/dist/lib/commands.test.js +111 -0
  107. package/dist/lib/theme.d.ts +25 -0
  108. package/dist/lib/theme.d.ts.map +1 -0
  109. package/dist/lib/theme.js +103 -0
  110. package/dist/lib/utils.d.ts +3 -0
  111. package/dist/lib/utils.d.ts.map +1 -0
  112. package/dist/lib/utils.js +5 -0
  113. package/dist/lib/workspace.d.ts +41 -0
  114. package/dist/lib/workspace.d.ts.map +1 -0
  115. package/dist/lib/workspace.js +82 -0
  116. package/package.json +87 -0
  117. package/src/styles/index.css +199 -0
@@ -0,0 +1,111 @@
1
+ import { afterEach, describe, expect, it } from 'vitest';
2
+ // Re-implement isInMoldable for testing (since it accesses window)
3
+ function isInMoldable() {
4
+ if (typeof window === 'undefined')
5
+ return false;
6
+ return window.parent !== window;
7
+ }
8
+ describe('commands utilities', () => {
9
+ describe('isInMoldable', () => {
10
+ const originalWindow = global.window;
11
+ afterEach(() => {
12
+ // Restore original window
13
+ if (originalWindow) {
14
+ global.window = originalWindow;
15
+ }
16
+ });
17
+ it('returns false when window is undefined (SSR)', () => {
18
+ // @ts-expect-error - intentionally setting to undefined
19
+ global.window = undefined;
20
+ // Re-evaluate with undefined window
21
+ const result = typeof window === 'undefined' ? false : window.parent !== window;
22
+ expect(result).toBe(false);
23
+ });
24
+ it('returns false when parent equals window (not in iframe)', () => {
25
+ // jsdom default: window.parent === window
26
+ expect(isInMoldable()).toBe(false);
27
+ });
28
+ it('returns true when parent differs from window (in iframe)', () => {
29
+ // Mock the iframe scenario
30
+ const mockParent = {};
31
+ const originalParent = window.parent;
32
+ Object.defineProperty(window, 'parent', {
33
+ value: mockParent,
34
+ writable: true,
35
+ configurable: true,
36
+ });
37
+ expect(isInMoldable()).toBe(true);
38
+ // Restore
39
+ Object.defineProperty(window, 'parent', {
40
+ value: originalParent,
41
+ writable: true,
42
+ configurable: true,
43
+ });
44
+ });
45
+ });
46
+ describe('CommandAction types', () => {
47
+ it('navigate action has path', () => {
48
+ const action = { type: 'navigate', path: '/settings' };
49
+ expect(action.type).toBe('navigate');
50
+ expect(action.path).toBe('/settings');
51
+ });
52
+ it('message action has payload', () => {
53
+ const action = { type: 'message', payload: { foo: 'bar' } };
54
+ expect(action.type).toBe('message');
55
+ expect(action.payload).toEqual({ foo: 'bar' });
56
+ });
57
+ it('focus action has target', () => {
58
+ const action = { type: 'focus', target: 'input-field' };
59
+ expect(action.type).toBe('focus');
60
+ expect(action.target).toBe('input-field');
61
+ });
62
+ });
63
+ describe('AppCommand structure', () => {
64
+ it('accepts valid command definition', () => {
65
+ const command = {
66
+ id: 'add-todo',
67
+ label: 'Add Todo',
68
+ shortcut: 'a',
69
+ icon: '➕',
70
+ group: 'Tasks',
71
+ action: { type: 'message', payload: {} },
72
+ };
73
+ expect(command.id).toBe('add-todo');
74
+ expect(command.label).toBe('Add Todo');
75
+ expect(command.shortcut).toBe('a');
76
+ expect(command.icon).toBe('➕');
77
+ expect(command.group).toBe('Tasks');
78
+ });
79
+ it('works with minimal required fields', () => {
80
+ const command = {
81
+ id: 'test',
82
+ label: 'Test Command',
83
+ action: { type: 'navigate', path: '/' },
84
+ };
85
+ expect(command.id).toBeDefined();
86
+ expect(command.label).toBeDefined();
87
+ expect(command.action).toBeDefined();
88
+ });
89
+ });
90
+ describe('CommandMessage structure', () => {
91
+ it('has correct type marker', () => {
92
+ const message = {
93
+ type: 'moldable:command',
94
+ command: 'add-todo',
95
+ payload: { text: 'Buy milk' },
96
+ };
97
+ expect(message.type).toBe('moldable:command');
98
+ expect(message.command).toBe('add-todo');
99
+ expect(message.payload).toEqual({ text: 'Buy milk' });
100
+ });
101
+ it('works without payload', () => {
102
+ const message = {
103
+ type: 'moldable:command',
104
+ command: 'refresh',
105
+ };
106
+ expect(message.type).toBe('moldable:command');
107
+ expect(message.command).toBe('refresh');
108
+ expect(message.payload).toBeUndefined();
109
+ });
110
+ });
111
+ });
@@ -0,0 +1,25 @@
1
+ import { type ReactNode } from 'react';
2
+ export type Theme = 'light' | 'dark' | 'system';
3
+ type ResolvedTheme = 'light' | 'dark';
4
+ interface ThemeContextValue {
5
+ theme: Theme;
6
+ resolvedTheme: ResolvedTheme;
7
+ setTheme: (theme: Theme) => void;
8
+ }
9
+ export declare function ThemeProvider({ children }: {
10
+ children: ReactNode;
11
+ }): import("react/jsx-runtime").JSX.Element;
12
+ export declare function useTheme(): ThemeContextValue;
13
+ /**
14
+ * Inline script to prevent theme flash on page load.
15
+ * Add this to your <head> in layout.tsx:
16
+ *
17
+ * ```tsx
18
+ * <head>
19
+ * <script dangerouslySetInnerHTML={{ __html: themeScript }} />
20
+ * </head>
21
+ * ```
22
+ */
23
+ export declare const themeScript = "\n(function() {\n var params = new URLSearchParams(window.location.search);\n var theme = params.get('theme');\n if (!theme) {\n theme = localStorage.getItem('moldable-theme');\n }\n if (theme === 'system' || !theme) {\n theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n }\n document.documentElement.classList.add(theme);\n})();\n";
24
+ export {};
25
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/lib/theme.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EAMf,MAAM,OAAO,CAAA;AAEd,MAAM,MAAM,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAA;AAC/C,KAAK,aAAa,GAAG,OAAO,GAAG,MAAM,CAAA;AAErC,UAAU,iBAAiB;IACzB,KAAK,EAAE,KAAK,CAAA;IACZ,aAAa,EAAE,aAAa,CAAA;IAC5B,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC;AA6CD,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CA0ClE;AAED,wBAAgB,QAAQ,sBAMvB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,kYAYvB,CAAA"}
@@ -0,0 +1,103 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useCallback, useContext, useEffect, useState, } from 'react';
4
+ const ThemeContext = createContext(null);
5
+ const STORAGE_KEY = 'moldable-theme';
6
+ function getSystemTheme() {
7
+ if (typeof window === 'undefined')
8
+ return 'dark';
9
+ return window.matchMedia('(prefers-color-scheme: dark)').matches
10
+ ? 'dark'
11
+ : 'light';
12
+ }
13
+ // Check for theme in URL parameter (used when embedded in iframe)
14
+ function getUrlTheme() {
15
+ if (typeof window === 'undefined')
16
+ return null;
17
+ const params = new URLSearchParams(window.location.search);
18
+ const theme = params.get('theme');
19
+ if (theme === 'light' || theme === 'dark' || theme === 'system') {
20
+ return theme;
21
+ }
22
+ return null;
23
+ }
24
+ function getStoredTheme() {
25
+ if (typeof window === 'undefined')
26
+ return 'system';
27
+ // URL parameter takes precedence (for iframe embedding)
28
+ const urlTheme = getUrlTheme();
29
+ if (urlTheme)
30
+ return urlTheme;
31
+ const stored = localStorage.getItem(STORAGE_KEY);
32
+ if (stored === 'light' || stored === 'dark' || stored === 'system') {
33
+ return stored;
34
+ }
35
+ return 'system';
36
+ }
37
+ function resolveTheme(theme) {
38
+ if (theme === 'system') {
39
+ return getSystemTheme();
40
+ }
41
+ return theme;
42
+ }
43
+ export function ThemeProvider({ children }) {
44
+ const [theme, setThemeState] = useState(() => getStoredTheme());
45
+ const [resolvedTheme, setResolvedTheme] = useState(() => resolveTheme(theme));
46
+ const setTheme = useCallback((newTheme) => {
47
+ setThemeState(newTheme);
48
+ localStorage.setItem(STORAGE_KEY, newTheme);
49
+ }, []);
50
+ // Apply theme to document
51
+ useEffect(() => {
52
+ const resolved = resolveTheme(theme);
53
+ setResolvedTheme(resolved);
54
+ const root = document.documentElement;
55
+ root.classList.remove('light', 'dark');
56
+ root.classList.add(resolved);
57
+ }, [theme]);
58
+ // Listen for system theme changes
59
+ useEffect(() => {
60
+ if (theme !== 'system')
61
+ return;
62
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
63
+ const handleChange = () => {
64
+ const resolved = getSystemTheme();
65
+ setResolvedTheme(resolved);
66
+ document.documentElement.classList.remove('light', 'dark');
67
+ document.documentElement.classList.add(resolved);
68
+ };
69
+ mediaQuery.addEventListener('change', handleChange);
70
+ return () => mediaQuery.removeEventListener('change', handleChange);
71
+ }, [theme]);
72
+ return (_jsx(ThemeContext.Provider, { value: { theme, resolvedTheme, setTheme }, children: children }));
73
+ }
74
+ export function useTheme() {
75
+ const context = useContext(ThemeContext);
76
+ if (!context) {
77
+ throw new Error('useTheme must be used within a ThemeProvider');
78
+ }
79
+ return context;
80
+ }
81
+ /**
82
+ * Inline script to prevent theme flash on page load.
83
+ * Add this to your <head> in layout.tsx:
84
+ *
85
+ * ```tsx
86
+ * <head>
87
+ * <script dangerouslySetInnerHTML={{ __html: themeScript }} />
88
+ * </head>
89
+ * ```
90
+ */
91
+ export const themeScript = `
92
+ (function() {
93
+ var params = new URLSearchParams(window.location.search);
94
+ var theme = params.get('theme');
95
+ if (!theme) {
96
+ theme = localStorage.getItem('moldable-theme');
97
+ }
98
+ if (theme === 'system' || !theme) {
99
+ theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
100
+ }
101
+ document.documentElement.classList.add(theme);
102
+ })();
103
+ `;
@@ -0,0 +1,3 @@
1
+ import { type ClassValue } from 'clsx';
2
+ export declare function cn(...inputs: ClassValue[]): string;
3
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAQ,MAAM,MAAM,CAAA;AAG5C,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
@@ -0,0 +1,5 @@
1
+ import { clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+ export function cn(...inputs) {
4
+ return twMerge(clsx(inputs));
5
+ }
@@ -0,0 +1,41 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * Header name used to pass workspace ID from client to server.
4
+ * Keep in sync with @moldable-ai/storage WORKSPACE_HEADER
5
+ */
6
+ export declare const WORKSPACE_HEADER = "x-moldable-workspace";
7
+ interface WorkspaceContextValue {
8
+ /**
9
+ * The current workspace ID (from URL param or default 'personal')
10
+ */
11
+ workspaceId: string;
12
+ /**
13
+ * Fetch function that automatically includes the workspace header.
14
+ * Use this for all API calls to ensure workspace isolation.
15
+ */
16
+ fetchWithWorkspace: typeof fetch;
17
+ }
18
+ export declare function WorkspaceProvider({ children }: {
19
+ children: ReactNode;
20
+ }): import("react/jsx-runtime").JSX.Element;
21
+ /**
22
+ * Hook to access current workspace context.
23
+ *
24
+ * @returns workspaceId and fetchWithWorkspace utility
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * function MyComponent() {
29
+ * const { workspaceId, fetchWithWorkspace } = useWorkspace()
30
+ *
31
+ * // Use in TanStack Query
32
+ * const { data } = useQuery({
33
+ * queryKey: ['notes', workspaceId],
34
+ * queryFn: () => fetchWithWorkspace('/api/notes').then(r => r.json()),
35
+ * })
36
+ * }
37
+ * ```
38
+ */
39
+ export declare function useWorkspace(): WorkspaceContextValue;
40
+ export {};
41
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/lib/workspace.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EAOf,MAAM,OAAO,CAAA;AAEd;;;GAGG;AACH,eAAO,MAAM,gBAAgB,yBAAyB,CAAA;AAEtD,UAAU,qBAAqB;IAC7B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,kBAAkB,EAAE,OAAO,KAAK,CAAA;CACjC;AAcD,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAuDtE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,0BAM3B"}
@@ -0,0 +1,82 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
4
+ /**
5
+ * Header name used to pass workspace ID from client to server.
6
+ * Keep in sync with @moldable-ai/storage WORKSPACE_HEADER
7
+ */
8
+ export const WORKSPACE_HEADER = 'x-moldable-workspace';
9
+ const WorkspaceContext = createContext(null);
10
+ /**
11
+ * Get workspace ID from URL query parameter.
12
+ * Used when app is embedded in Moldable desktop iframe.
13
+ */
14
+ function getUrlWorkspace() {
15
+ if (typeof window === 'undefined')
16
+ return null;
17
+ const params = new URLSearchParams(window.location.search);
18
+ return params.get('workspace');
19
+ }
20
+ export function WorkspaceProvider({ children }) {
21
+ const [workspaceId, setWorkspaceId] = useState('personal');
22
+ // Read workspace from URL on mount and when URL changes
23
+ useEffect(() => {
24
+ const urlWorkspace = getUrlWorkspace();
25
+ if (urlWorkspace) {
26
+ setWorkspaceId(urlWorkspace);
27
+ }
28
+ }, []);
29
+ // Listen for URL changes (e.g., when workspace switches and iframe reloads)
30
+ useEffect(() => {
31
+ const handleUrlChange = () => {
32
+ const urlWorkspace = getUrlWorkspace();
33
+ if (urlWorkspace && urlWorkspace !== workspaceId) {
34
+ setWorkspaceId(urlWorkspace);
35
+ }
36
+ };
37
+ // Check on popstate (back/forward navigation)
38
+ window.addEventListener('popstate', handleUrlChange);
39
+ return () => {
40
+ window.removeEventListener('popstate', handleUrlChange);
41
+ };
42
+ }, [workspaceId]);
43
+ // Create a fetch wrapper that adds the workspace header
44
+ const fetchWithWorkspace = useCallback(async (input, init) => {
45
+ const headers = new Headers(init?.headers);
46
+ headers.set(WORKSPACE_HEADER, workspaceId);
47
+ return fetch(input, {
48
+ ...init,
49
+ headers,
50
+ });
51
+ }, [workspaceId]);
52
+ const value = useMemo(() => ({
53
+ workspaceId,
54
+ fetchWithWorkspace,
55
+ }), [workspaceId, fetchWithWorkspace]);
56
+ return (_jsx(WorkspaceContext.Provider, { value: value, children: children }));
57
+ }
58
+ /**
59
+ * Hook to access current workspace context.
60
+ *
61
+ * @returns workspaceId and fetchWithWorkspace utility
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * function MyComponent() {
66
+ * const { workspaceId, fetchWithWorkspace } = useWorkspace()
67
+ *
68
+ * // Use in TanStack Query
69
+ * const { data } = useQuery({
70
+ * queryKey: ['notes', workspaceId],
71
+ * queryFn: () => fetchWithWorkspace('/api/notes').then(r => r.json()),
72
+ * })
73
+ * }
74
+ * ```
75
+ */
76
+ export function useWorkspace() {
77
+ const context = useContext(WorkspaceContext);
78
+ if (!context) {
79
+ throw new Error('useWorkspace must be used within a WorkspaceProvider');
80
+ }
81
+ return context;
82
+ }
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "@moldable-ai/ui",
3
+ "version": "0.1.1",
4
+ "description": "Shared UI components for Moldable applications",
5
+ "author": "Desiderata LLC",
6
+ "license": "Elastic-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/moldable-ai/moldable.git",
10
+ "directory": "packages/ui"
11
+ },
12
+ "homepage": "https://moldable.sh",
13
+ "bugs": {
14
+ "url": "https://github.com/moldable-ai/moldable/issues"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "type": "module",
20
+ "main": "dist/index.js",
21
+ "types": "dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "require": "./dist/index.js",
25
+ "import": "./dist/index.js",
26
+ "types": "./dist/index.d.ts"
27
+ },
28
+ "./styles": "./src/styles/index.css"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "src/styles",
33
+ "LICENSE"
34
+ ],
35
+ "dependencies": {
36
+ "@radix-ui/react-alert-dialog": "^1.1.15",
37
+ "@radix-ui/react-checkbox": "^1.3.2",
38
+ "@radix-ui/react-collapsible": "^1.1.11",
39
+ "@radix-ui/react-dialog": "^1.1.15",
40
+ "@radix-ui/react-dropdown-menu": "^2.1.15",
41
+ "@radix-ui/react-label": "^2.1.7",
42
+ "@radix-ui/react-popover": "^1.1.15",
43
+ "@radix-ui/react-scroll-area": "^1.2.2",
44
+ "@radix-ui/react-select": "^2.2.5",
45
+ "@radix-ui/react-separator": "^1.1.7",
46
+ "@radix-ui/react-slot": "^1.2.4",
47
+ "@radix-ui/react-switch": "^1.2.5",
48
+ "@radix-ui/react-tabs": "^1.1.13",
49
+ "@radix-ui/react-tooltip": "^1.2.7",
50
+ "@tailwindcss/typography": "^0.5.16",
51
+ "class-variance-authority": "^0.7.1",
52
+ "clsx": "^2.1.1",
53
+ "cmdk": "1.1.0",
54
+ "framer-motion": "^12.15.0",
55
+ "lucide-react": "^0.473.0",
56
+ "react-markdown": "^10.1.0",
57
+ "rehype-raw": "^7.0.0",
58
+ "remark-gfm": "^4.0.1",
59
+ "shiki": "^3.21.0",
60
+ "tailwind-merge": "^2.6.0",
61
+ "tailwindcss": "^4.1.10",
62
+ "tw-animate-css": "^1.3.4"
63
+ },
64
+ "devDependencies": {
65
+ "@types/node": "^24.0.3",
66
+ "@types/react": "^19.1.6",
67
+ "@types/react-dom": "^19.1.6",
68
+ "jsdom": "^26.1.0",
69
+ "typescript": "^5.8.3",
70
+ "vitest": "^3.2.4",
71
+ "@moldable-ai/eslint-config": "0.0.1",
72
+ "@moldable-ai/prettier-config": "0.0.1",
73
+ "@moldable-ai/typescript-config": "0.0.1"
74
+ },
75
+ "peerDependencies": {
76
+ "react": "^19.0.0",
77
+ "react-dom": "^19.0.0"
78
+ },
79
+ "scripts": {
80
+ "build": "tsc",
81
+ "dev": "tsc --watch",
82
+ "lint": "eslint . --ext .ts,.tsx --max-warnings 0",
83
+ "format": "prettier --write . --ignore-path ../../.prettierignore",
84
+ "check-types": "tsc --noEmit",
85
+ "test": "vitest run"
86
+ }
87
+ }
@@ -0,0 +1,199 @@
1
+ @import 'tailwindcss';
2
+ @import 'tw-animate-css';
3
+
4
+ @plugin "@tailwindcss/typography";
5
+
6
+ @custom-variant dark (&:is(.dark *));
7
+
8
+ @theme inline {
9
+ --radius-sm: calc(var(--radius) - 4px);
10
+ --radius-md: calc(var(--radius) - 2px);
11
+ --radius-lg: var(--radius);
12
+ --radius-xl: calc(var(--radius) + 4px);
13
+ --color-background: var(--background);
14
+ --color-foreground: var(--foreground);
15
+ --color-card: var(--card);
16
+ --color-card-foreground: var(--card-foreground);
17
+ --color-popover: var(--popover);
18
+ --color-popover-foreground: var(--popover-foreground);
19
+ --color-primary: var(--primary);
20
+ --color-primary-foreground: var(--primary-foreground);
21
+ --color-secondary: var(--secondary);
22
+ --color-secondary-foreground: var(--secondary-foreground);
23
+ --color-muted: var(--muted);
24
+ --color-muted-foreground: var(--muted-foreground);
25
+ --color-accent: var(--accent);
26
+ --color-accent-foreground: var(--accent-foreground);
27
+ --color-destructive: var(--destructive);
28
+ --color-destructive-foreground: var(--destructive-foreground);
29
+ --color-border: var(--border);
30
+ --color-input: var(--input);
31
+ --color-ring: var(--ring);
32
+ --color-chart-1: var(--chart-1);
33
+ --color-chart-2: var(--chart-2);
34
+ --color-chart-3: var(--chart-3);
35
+ --color-chart-4: var(--chart-4);
36
+ --color-chart-5: var(--chart-5);
37
+ --color-sidebar: var(--sidebar);
38
+ --color-sidebar-foreground: var(--sidebar-foreground);
39
+ --color-sidebar-primary: var(--sidebar-primary);
40
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
41
+ --color-sidebar-accent: var(--sidebar-accent);
42
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
43
+ --color-sidebar-border: var(--sidebar-border);
44
+ --color-sidebar-ring: var(--sidebar-ring);
45
+ --color-warning: var(--warning);
46
+ --color-warning-foreground: var(--warning-foreground);
47
+ --color-success: var(--success);
48
+ --color-success-foreground: var(--success-foreground);
49
+ --color-status-running: var(--status-running);
50
+ --color-status-stopped: var(--status-stopped);
51
+ --color-status-pending: var(--status-pending);
52
+ --color-status-error: var(--status-error);
53
+ --color-terminal: var(--terminal);
54
+ --color-terminal-header: var(--terminal-header);
55
+ --color-terminal-foreground: var(--terminal-foreground);
56
+ --color-terminal-muted: var(--terminal-muted);
57
+ --color-terminal-border: var(--terminal-border);
58
+ --color-terminal-stdout: var(--terminal-stdout);
59
+ --color-terminal-stderr: var(--terminal-stderr);
60
+ --color-terminal-error: var(--terminal-error);
61
+ --chat-safe-padding: var(--chat-safe-padding);
62
+ }
63
+
64
+ :root {
65
+ --radius: 0.65rem;
66
+ --background: oklch(1 0 0);
67
+ --foreground: oklch(0.141 0.005 285.823);
68
+ --card: oklch(1 0 0);
69
+ --card-foreground: oklch(0.141 0.005 285.823);
70
+ --popover: oklch(1 0 0);
71
+ --popover-foreground: oklch(0.141 0.005 285.823);
72
+ --primary: oklch(0.7145 0.185 45.03);
73
+ --primary-foreground: oklch(0.985 0 0);
74
+ --secondary: oklch(0.967 0.001 286.375);
75
+ --secondary-foreground: oklch(0.21 0.006 285.885);
76
+ --muted: oklch(0.967 0.001 286.375);
77
+ --muted-foreground: oklch(0.552 0.016 285.938);
78
+ --accent: oklch(0.967 0.001 286.375);
79
+ --accent-foreground: oklch(0.21 0.006 285.885);
80
+ --destructive: oklch(0.5866 0.1994 31.76);
81
+ --destructive-foreground: oklch(0.985 0 0);
82
+ --border: oklch(0.92 0.004 286.32);
83
+ --input: oklch(0.92 0.004 286.32);
84
+ --ring: oklch(0.7145 0.185 45.03);
85
+ --chart-1: oklch(0.646 0.222 41.116);
86
+ --chart-2: oklch(0.6 0.118 184.704);
87
+ --chart-3: oklch(0.398 0.07 227.392);
88
+ --chart-4: oklch(0.828 0.189 84.429);
89
+ --chart-5: oklch(0.769 0.188 70.08);
90
+ --sidebar: oklch(0.967 0.001 286.375);
91
+ --sidebar-foreground: oklch(0.141 0.005 285.823);
92
+ --sidebar-primary: oklch(0.7145 0.185 45.03);
93
+ --sidebar-primary-foreground: oklch(0.985 0 0);
94
+ --sidebar-accent: oklch(0.9212 0.0029 264.54);
95
+ --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
96
+ --sidebar-border: oklch(0.92 0.004 286.32);
97
+ --sidebar-ring: oklch(0.7145 0.185 45.03);
98
+ --warning: oklch(0.75 0.15 75);
99
+ --warning-foreground: oklch(0.35 0.1 75);
100
+ --success: oklch(0.65 0.2 145);
101
+ --success-foreground: oklch(0.985 0 0);
102
+ --status-running: oklch(0.65 0.2 145);
103
+ --status-stopped: oklch(0.552 0.016 285.938);
104
+ --status-pending: oklch(0.75 0.15 75);
105
+ --status-error: oklch(0.5866 0.1994 31.76);
106
+ /* Terminal - stays dark in both modes */
107
+ --terminal: oklch(0.16 0 0);
108
+ --terminal-header: oklch(0.22 0 0);
109
+ --terminal-foreground: oklch(0.97 0 0);
110
+ --terminal-muted: oklch(0.55 0 0);
111
+ --terminal-border: oklch(0.32 0 0);
112
+ --terminal-stdout: oklch(0.75 0 0);
113
+ --terminal-stderr: oklch(0.78 0.12 75);
114
+ --terminal-error: oklch(0.7 0.16 25);
115
+ --chat-safe-padding: 104px;
116
+ }
117
+
118
+ .dark {
119
+ --background: oklch(0.235 0 0);
120
+ --foreground: oklch(0.985 0 0);
121
+ --card: oklch(0.269 0.0018 286.28);
122
+ --card-foreground: oklch(0.985 0 0);
123
+ --popover: oklch(0.269 0.0018 286.28);
124
+ --popover-foreground: oklch(0.985 0 0);
125
+ --primary: oklch(0.7145 0.185 45.03);
126
+ --primary-foreground: oklch(0.985 0 0);
127
+ --secondary: oklch(0.3375 0.0035 286.21);
128
+ --secondary-foreground: oklch(0.985 0 0);
129
+ --muted: oklch(0.3375 0.0035 286.21);
130
+ --muted-foreground: oklch(0.705 0.015 286.067);
131
+ --accent: oklch(0.3375 0.0035 286.21);
132
+ --accent-foreground: oklch(0.985 0 0);
133
+ --destructive: oklch(0.704 0.191 22.216);
134
+ --destructive-foreground: oklch(0.985 0 0);
135
+ --border: oklch(1 0 0 / 10%);
136
+ --input: oklch(1 0 0 / 15%);
137
+ --ring: oklch(0.7145 0.185 45.03);
138
+ --chart-1: oklch(0.7145 0.185 45.03);
139
+ --chart-2: oklch(0.696 0.17 162.48);
140
+ --chart-3: oklch(0.769 0.188 70.08);
141
+ --chart-4: oklch(0.627 0.265 303.9);
142
+ --chart-5: oklch(0.645 0.246 16.439);
143
+ --sidebar: oklch(0.235 0 0);
144
+ --sidebar-foreground: oklch(0.985 0 0);
145
+ --sidebar-primary: oklch(0.7145 0.185 45.03);
146
+ --sidebar-primary-foreground: oklch(0.985 0 0);
147
+ --sidebar-accent: oklch(0.3375 0.0035 286.21);
148
+ --sidebar-accent-foreground: oklch(0.985 0 0);
149
+ --sidebar-border: oklch(1 0 0 / 10%);
150
+ --sidebar-ring: oklch(0.7145 0.185 45.03);
151
+ --warning: oklch(0.8 0.12 75);
152
+ --warning-foreground: oklch(0.25 0.08 75);
153
+ --success: oklch(0.7 0.18 145);
154
+ --success-foreground: oklch(0.235 0 0);
155
+ --status-running: oklch(0.7 0.18 145);
156
+ --status-stopped: oklch(0.705 0.015 286.067);
157
+ --status-pending: oklch(0.8 0.12 75);
158
+ --status-error: oklch(0.704 0.191 22.216);
159
+ /* Terminal - same dark look in dark mode */
160
+ --terminal: oklch(0.16 0 0);
161
+ --terminal-header: oklch(0.22 0 0);
162
+ --terminal-foreground: oklch(0.97 0 0);
163
+ --terminal-muted: oklch(0.55 0 0);
164
+ --terminal-border: oklch(0.32 0 0);
165
+ --terminal-stdout: oklch(0.75 0 0);
166
+ --terminal-stderr: oklch(0.78 0.12 75);
167
+ --terminal-error: oklch(0.7 0.16 25);
168
+ --chat-safe-padding: 104px;
169
+ }
170
+
171
+ @layer base {
172
+ * {
173
+ @apply border-border outline-ring/50;
174
+ }
175
+ body {
176
+ @apply bg-background text-foreground;
177
+ }
178
+ ::selection {
179
+ background-color: var(--primary);
180
+ color: var(--primary-foreground);
181
+ }
182
+ }
183
+
184
+ @layer utilities {
185
+ .scrollbar-hide {
186
+ &::-webkit-scrollbar {
187
+ display: none;
188
+ }
189
+ -ms-overflow-style: none;
190
+ scrollbar-width: none;
191
+ }
192
+ }
193
+
194
+ /* Shiki dual-theme support: override inline styles in dark mode */
195
+ .dark .shiki,
196
+ .dark .shiki span {
197
+ color: var(--shiki-dark) !important;
198
+ background-color: var(--shiki-dark-bg) !important;
199
+ }