This directory contains the comprehensive TypeScript/React frontend architecture for Datalayer Core. It provides a complete component library, state management, hooks, utilities, and API integration for building modern web applications on the Datalayer platform.
src/
βββ api/ # API layer and Jupyter integration
βββ components/ # React component library (70+ components)
βββ config/ # Configuration utilities
βββ examples/ # Usage examples
βββ hooks/ # Custom React hooks (25+ hooks)
βββ i18n/ # Internationalization
βββ models/ # TypeScript type definitions (70+ models)
βββ routes/ # Routing configuration
βββ state/ # Zustand state management
βββ stories/ # Storybook stories
βββ theme/ # Theme and styling
βββ utils/ # Utility functions (20+ utilities)
βββ mocks/ # Testing mocks
npm install# Start development server
npm run dev
# Run Storybook for component development
npm run storybook
# Run tests
npm run test
# Build for production
npm run buildimport { BoringAvatar, UserProfileAvatar } from '@datalayer/core';
// Generate unique avatars
<BoringAvatar
size={40}
name="John Doe"
variant="marble"
colors={["#92A1C6", "#146A7C", "#F0AB3D"]}
/>
// User profile avatar with fallback
<UserProfileAvatar
user={{ name: "John Doe", avatarUrl: "https://..." }}
size="large"
/>import {
LongActionButton,
DownloadCSVButton,
UploadButton
} from '@datalayer/core';
// Long-running action with loading state
<LongActionButton
onClick={async () => await longRunningTask()}
loadingText="Processing..."
variant="primary"
>
Start Analysis
</LongActionButton>
// CSV download functionality
<DownloadCSVButton
data={[
{ name: "John", age: 30 },
{ name: "Jane", age: 25 }
]}
filename="users.csv"
/>
// File upload with validation
<UploadButton
accept=".csv,.json"
maxSize={10 * 1024 * 1024} // 10MB
onUpload={(files) => handleFiles(files)}
multiple
>
Upload Files
</UploadButton>import {
CenteredSpinner,
DatalayerBox,
CodePreview,
Markdown
} from '@datalayer/core';
// Loading spinner
<CenteredSpinner size="large" />
// Branded container
<DatalayerBox variant="elevated">
<h2>Dashboard Content</h2>
</DatalayerBox>
// Syntax-highlighted code
<CodePreview
code={`
const hello = (name: string) => {
console.log(\`Hello, \${name}!\`);
};
`}
language="typescript"
showLineNumbers
/>
// Markdown rendering
<Markdown content="# Hello World\nThis is **bold** text." />import { EChartsReact } from '@datalayer/core';
const chartOptions = {
title: { text: 'Sample Chart' },
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
yAxis: { type: 'value' },
series: [
{
data: [120, 200, 150],
type: 'line',
},
],
};
<EChartsReact option={chartOptions} style={{ height: '400px' }} />;import {
ProgressBar,
ProgressRing,
ConsumptionBar,
CreditsIndicator
} from '@datalayer/core';
// Linear progress bar
<ProgressBar value={75} max={100} label="Upload Progress" />
// Circular progress ring
<ProgressRing
value={60}
size={80}
strokeWidth={8}
color="#0066CC"
/>
// Resource consumption
<ConsumptionBar
used={750}
total={1000}
unit="MB"
warningThreshold={0.8}
/>
// Credits display
<CreditsIndicator
current={150}
total={500}
showUsage
/>import {
JupyterNotebook,
JupyterNotebookToolbar,
RuntimePickerNotebook
} from '@datalayer/core';
// Full notebook viewer
<JupyterNotebook
notebookId="notebook-123"
readonly={false}
showLineNumbers
theme="light"
/>
// Notebook toolbar
<JupyterNotebookToolbar
notebook={notebookData}
onSave={handleSave}
onRun={handleRun}
onExport={handleExport}
/>
// Runtime selection for notebooks
<RuntimePickerNotebook
currentRuntime="python-runtime-1"
onRuntimeChange={handleRuntimeChange}
availableRuntimes={runtimes}
/>import {
RuntimePickerBase,
RuntimeLauncherDialog,
RuntimeVariables,
RuntimeReservationControl
} from '@datalayer/core';
// Basic runtime picker
<RuntimePickerBase
runtimes={availableRuntimes}
selectedRuntime={currentRuntime}
onSelect={setCurrentRuntime}
/>
// Runtime launcher modal
<RuntimeLauncherDialog
isOpen={showLauncher}
onClose={() => setShowLauncher(false)}
onLaunch={handleRuntimeLaunch}
environments={environments}
/>
// Runtime variables inspector
<RuntimeVariables
runtimeId="runtime-123"
variables={runtimeVariables}
onVariableUpdate={handleVariableUpdate}
/>import { SubdomainNavBar } from '@datalayer/core';
<SubdomainNavBar
user={currentUser}
organization={currentOrg}
navigation={[
{ label: 'Dashboard', href: '/dashboard' },
{ label: 'Notebooks', href: '/notebooks' },
{ label: 'Runtimes', href: '/runtimes' },
]}
onSignOut={handleSignOut}
/>;import { SubNav } from '@datalayer/core';
<SubNav
items={[
{ key: 'overview', label: 'Overview', href: '/overview' },
{ key: 'settings', label: 'Settings', href: '/settings' },
{ key: 'members', label: 'Members', href: '/members' },
]}
activeKey="overview"
/>;import { OrganizationSelect, SpaceSelect } from '@datalayer/core';
// Organization selector
<OrganizationSelect
organizations={userOrganizations}
selected={currentOrganization}
onSelect={setCurrentOrganization}
/>
// Space selector
<SpaceSelect
spaces={availableSpaces}
selected={currentSpace}
onSelect={setCurrentSpace}
organization={currentOrganization}
/>import { ContentsBrowser, ContentsItems } from '@datalayer/core';
// File browser component
<ContentsBrowser
path="/notebooks"
onNavigate={handlePathChange}
onFileSelect={handleFileSelect}
allowUpload
allowNewFolder
/>
// File list component
<ContentsItems
items={fileItems}
selectedItems={selectedFiles}
onSelectionChange={setSelectedFiles}
onItemAction={handleItemAction}
/>import { useUser, useIAM, useAuthorization } from '@datalayer/core';
function UserProfile() {
const { user, loading, error } = useUser();
const { isAuthenticated, login, logout } = useIAM();
const { hasPermission } = useAuthorization();
if (loading) return <div>Loading...</div>;
if (!isAuthenticated) return <button onClick={login}>Login</button>;
return (
<div>
<h1>Welcome, {user?.name}</h1>
{hasPermission('admin') && <AdminPanel />}
<button onClick={logout}>Logout</button>
</div>
);
}import { useDatalayer, useRuntimes, useNotebookAIAgent } from '@datalayer/core';
function RuntimeDashboard() {
// Main Datalayer platform integration
const { client, isConnected } = useDatalayer();
// Runtime management
const {
runtimes,
loading,
createRuntime,
deleteRuntime,
startRuntime,
stopRuntime,
} = useRuntimes();
// AI agent for notebooks
const { agent, executeCode, getCompletion } = useNotebookAIAgent();
const handleCreateRuntime = async () => {
await createRuntime({
name: 'new-runtime',
environment: 'python-3.11',
});
};
return (
<div>
<h2>Runtimes ({runtimes.length})</h2>
<button onClick={handleCreateRuntime}>Create Runtime</button>
{runtimes.map(runtime => (
<div key={runtime.id}>
<span>
{runtime.name} - {runtime.status}
</span>
<button onClick={() => startRuntime(runtime.id)}>Start</button>
<button onClick={() => stopRuntime(runtime.id)}>Stop</button>
</div>
))}
</div>
);
}import {
useToast,
useBackdrop,
useScreencapture,
useUpload,
useVisibilityObserver,
} from '@datalayer/core';
function InteractiveComponent() {
const { showToast } = useToast();
const { showBackdrop, hideBackdrop } = useBackdrop();
const { takeScreencapture } = useScreencapture();
const { uploadFiles, uploading, progress } = useUpload();
const { ref, isVisible } = useVisibilityObserver();
const handleAction = async () => {
showBackdrop('Processing...');
try {
await someAsyncAction();
showToast('Success!', 'success');
} catch (error) {
showToast('Error occurred', 'error');
} finally {
hideBackdrop();
}
};
const handleScreencapture = async () => {
const screenshot = await takeScreencapture(ref.current);
// Use screenshot data
};
return (
<div ref={ref}>
<button onClick={handleAction}>Perform Action</button>
<button onClick={handleScreencapture}>Take Screencapture</button>
{isVisible && <div>Component is visible!</div>}
<input type="file" onChange={e => uploadFiles(e.target.files)} multiple />
{uploading && <div>Upload progress: {progress}%</div>}
</div>
);
}import { useCache, useWindowSize } from '@datalayer/core';
function CachedDataComponent() {
const { width, height } = useWindowSize();
const { data, loading, error, refresh } = useCache(
'expensive-data',
async () => {
const response = await fetch('/api/expensive-data');
return response.json();
},
{ ttl: 5 * 60 * 1000 }, // 5 minutes cache
);
// Responsive behavior based on window size
const isMobile = width < 768;
return (
<div className={isMobile ? 'mobile-layout' : 'desktop-layout'}>
{loading ? (
<div>Loading...</div>
) : error ? (
<div>Error: {error.message}</div>
) : (
<div>
<pre>{JSON.stringify(data, null, 2)}</pre>
<button onClick={refresh}>Refresh</button>
</div>
)}
</div>
);
}import { useCoreState } from '@datalayer/core';
function AppHeader() {
const {
theme,
setTheme,
sidebarOpen,
setSidebarOpen,
notifications,
addNotification,
} = useCoreState();
return (
<header>
<button onClick={() => setSidebarOpen(!sidebarOpen)}>
Toggle Sidebar
</button>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
<div className="notifications">
{notifications.map(notification => (
<div key={notification.id}>{notification.message}</div>
))}
</div>
</header>
);
}import { useCache } from '@datalayer/core';
function OrganizationDashboard() {
const {
useUserOrganizations,
useUserSpaces,
useCreateSpace,
useDeleteSpace,
} = useCache();
const { data: organizations = [] } = useUserOrganizations();
const { data: spaces = [] } = useUserSpaces();
const createSpace = useCreateSpace();
const deleteSpace = useDeleteSpace();
const currentOrganization = organizations[0];
const handleCreateSpace = async () => {
await createSpace.mutateAsync({
name: 'New Space',
description: 'A new workspace',
organizationId: currentOrganization?.id,
});
};
return (
<div>
<h1>{currentOrganization?.name}</h1>
<div>
<h2>Spaces</h2>
<button onClick={handleCreateSpace}>Create Space</button>
{spaces.map(space => (
<div key={space.id}>
<span>{space.name}</span>
<button onClick={() => setCurrentSpace(space)}>Select</button>
<button onClick={() => deleteSpace(space.id)}>Delete</button>
</div>
))}
</div>
</div>
);
}import { useRuntimesState } from '@datalayer/core';
function RuntimeManager() {
const {
runtimes,
loading,
error,
selectedRuntime,
setSelectedRuntime,
createRuntime,
updateRuntime,
deleteRuntime,
fetchRuntimes,
} = useRuntimesState();
const handleRuntimeAction = async (runtimeId: string, action: string) => {
try {
await updateRuntime(runtimeId, { action });
} catch (error) {
console.error('Runtime action failed:', error);
}
};
return (
<div>
<h2>Runtime Management</h2>
{loading && <div>Loading runtimes...</div>}
{error && <div>Error: {error}</div>}
{runtimes.map(runtime => (
<div key={runtime.id} className="runtime-card">
<h3>{runtime.name}</h3>
<p>Status: {runtime.status}</p>
<p>Environment: {runtime.environment}</p>
<div className="runtime-actions">
<button onClick={() => handleRuntimeAction(runtime.id, 'start')}>
Start
</button>
<button onClick={() => handleRuntimeAction(runtime.id, 'stop')}>
Stop
</button>
<button onClick={() => handleRuntimeAction(runtime.id, 'restart')}>
Restart
</button>
<button onClick={() => deleteRuntime(runtime.id)}>Delete</button>
</div>
</div>
))}
</div>
);
}import {
uniqueBy,
groupBy,
sortBy,
chunk,
flatten,
} from '@datalayer/core/utils';
// Remove duplicates by property
const uniqueUsers = uniqueBy(users, 'id');
// Group items by property
const usersByRole = groupBy(users, 'role');
// Sort with multiple criteria
const sortedUsers = sortBy(users, ['lastName', 'firstName']);
// Split array into chunks
const batches = chunk(largeArray, 10);
// Flatten nested arrays
const flatList = flatten(nestedArrays);import {
formatRelativeTime,
formatDate,
parseDate,
addDays,
isToday,
} from '@datalayer/core/utils';
// Relative time formatting
const relativeTime = formatRelativeTime(new Date('2023-01-01'));
// "2 months ago"
// Date formatting
const formattedDate = formatDate(new Date(), 'yyyy-MM-dd');
// "2023-03-15"
// Date parsing
const parsedDate = parseDate('2023-03-15');
// Date arithmetic
const futureDate = addDays(new Date(), 7);
// Date checks
const checkToday = isToday(someDate);import {
downloadFile,
readFileAsText,
validateFileType,
formatFileSize,
getFileExtension,
} from '@datalayer/core/utils';
// Download file
downloadFile(data, 'report.csv', 'text/csv');
// Read file content
const content = await readFileAsText(file);
// Validate file type
const isValidImage = validateFileType(file, ['image/png', 'image/jpeg']);
// Format file size
const sizeString = formatFileSize(file.size);
// "2.5 MB"
// Get file extension
const extension = getFileExtension(file.name);
// "png"import {
capitalize,
camelCase,
kebabCase,
truncate,
stripHtml,
generateSlug,
} from '@datalayer/core/utils';
// String transformations
const title = capitalize('hello world');
// "Hello world"
const camelCased = camelCase('hello-world-test');
// "helloWorldTest"
const kebabCased = kebabCase('HelloWorldTest');
// "hello-world-test"
// String manipulation
const truncated = truncate('Long text here...', 10);
// "Long text..."
const plainText = stripHtml('<p>Hello <strong>world</strong></p>');
// "Hello world"
const slug = generateSlug('Hello World! 123');
// "hello-world-123"import {
formatNumber,
formatCurrency,
formatPercentage,
clamp,
randomBetween,
} from '@datalayer/core/utils';
// Number formatting
const formattedNum = formatNumber(1234567);
// "1,234,567"
const currency = formatCurrency(99.99, 'USD');
// "$99.99"
const percentage = formatPercentage(0.756, 2);
// "75.60%"
// Number manipulation
const clamped = clamp(15, 0, 10);
// 10
const random = randomBetween(1, 100);
// Random number between 1 and 100import { DatalayerThemeProvider } from '@datalayer/core';
function App() {
return (
<DatalayerThemeProvider theme="light">
<YourAppContent />
</DatalayerThemeProvider>
);
}import { createDatalayerTheme } from '@datalayer/core';
const customTheme = createDatalayerTheme({
palette: {
primary: {
main: '#1976d2',
light: '#42a5f5',
dark: '#1565c0',
},
secondary: {
main: '#dc004e',
},
},
typography: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
},
spacing: 8,
breakpoints: {
xs: 0,
sm: 600,
md: 960,
lg: 1280,
xl: 1920,
},
});import { render, screen, fireEvent } from '@testing-library/react';
import { MyComponent } from './MyComponent';
describe('MyComponent', () => {
it('renders correctly', () => {
render(<MyComponent title="Test" />);
expect(screen.getByText('Test')).toBeInTheDocument();
});
it('handles click events', () => {
const handleClick = jest.fn();
render(<MyComponent onClick={handleClick} />);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalled();
});
});import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
describe('useCounter', () => {
it('increments counter', () => {
const { result } = renderHook(() => useCounter(0));
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
});import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
variant: 'primary',
children: 'Button',
},
};
export const Secondary: Story = {
args: {
variant: 'secondary',
children: 'Button',
},
};// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
},
});// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}# Build library
npm run build:lib
# Build application
npm run build
# Generate TypeDoc documentation
npm run typedocThe package exports components, hooks, utilities, and types:
import {
// Components
DatalayerBox,
UserProfileAvatar,
JupyterNotebook,
// Hooks
useUser,
useRuntimes,
useDatalayer,
// Utilities
formatDate,
downloadFile,
generateSlug,
// Types
User,
Runtime,
Organization,
} from '@datalayer/core';- Follow the existing code structure and patterns
- Add comprehensive TypeScript types
- Include Storybook stories for new components
- Write tests for all functionality
- Update documentation as needed
This project is licensed under the Modified BSD License.
Built with β€οΈ using React, TypeScript, and modern web technologies.