@papernote/ui 1.0.0 → 1.2.0
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 +21 -21
- package/README.md +455 -445
- package/dist/components/CurrencyInput.d.ts +52 -0
- package/dist/components/CurrencyInput.d.ts.map +1 -0
- package/dist/components/DataTable.d.ts +3 -1
- package/dist/components/DataTable.d.ts.map +1 -1
- package/dist/components/Modal.d.ts.map +1 -1
- package/dist/components/Page.d.ts +2 -0
- package/dist/components/Page.d.ts.map +1 -1
- package/dist/components/PageLayout.d.ts +5 -1
- package/dist/components/PageLayout.d.ts.map +1 -1
- package/dist/components/Spreadsheet.d.ts +129 -0
- package/dist/components/Spreadsheet.d.ts.map +1 -0
- package/dist/components/Tabs.d.ts +5 -1
- package/dist/components/Tabs.d.ts.map +1 -1
- package/dist/components/index.d.ts +6 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.d.ts +336 -5
- package/dist/index.esm.js +51152 -174
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +51145 -143
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1187 -11
- package/dist/utils/excelExport.d.ts +143 -0
- package/dist/utils/excelExport.d.ts.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/package.json +13 -3
- package/src/components/AdminModal.css +49 -49
- package/src/components/CurrencyInput.stories.tsx +290 -0
- package/src/components/CurrencyInput.tsx +193 -0
- package/src/components/DataTable.stories.tsx +87 -0
- package/src/components/DataTable.tsx +149 -37
- package/src/components/Modal.stories.tsx +64 -0
- package/src/components/Modal.tsx +15 -2
- package/src/components/Page.stories.tsx +76 -0
- package/src/components/Page.tsx +35 -3
- package/src/components/PageLayout.stories.tsx +75 -0
- package/src/components/PageLayout.tsx +28 -9
- package/src/components/RoleManager.css +10 -10
- package/src/components/Spreadsheet.css +216 -0
- package/src/components/Spreadsheet.stories.tsx +362 -0
- package/src/components/Spreadsheet.tsx +351 -0
- package/src/components/SpreadsheetSimple.stories.tsx +27 -0
- package/src/components/Tabs.stories.tsx +31 -0
- package/src/components/Tabs.tsx +28 -4
- package/src/components/TimePicker.tsx +1 -1
- package/src/components/Toast.tsx +9 -9
- package/src/components/__tests__/Input.test.tsx +22 -26
- package/src/components/index.ts +11 -2
- package/src/styles/index.css +44 -6
- package/src/utils/excelExport.stories.tsx +535 -0
- package/src/utils/excelExport.ts +225 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/sqlToNaturalLanguage.ts +1 -1
- package/tailwind.config.js +253 -253
- package/dist/components/Button.stories.d.ts +0 -51
- package/dist/components/Button.stories.d.ts.map +0 -1
- package/dist/components/ChartVisualizationUI.d.ts +0 -21
- package/dist/components/ChartVisualizationUI.d.ts.map +0 -1
- package/dist/components/ChatUI.d.ts +0 -23
- package/dist/components/ChatUI.d.ts.map +0 -1
- package/dist/components/CommissionDashboardUI.d.ts +0 -25
- package/dist/components/CommissionDashboardUI.d.ts.map +0 -1
- package/dist/components/DataTable.stories.d.ts +0 -23
- package/dist/components/DataTable.stories.d.ts.map +0 -1
- package/dist/components/FormField.d.ts +0 -35
- package/dist/components/FormField.d.ts.map +0 -1
- package/dist/components/Input.stories.d.ts +0 -366
- package/dist/components/Input.stories.d.ts.map +0 -1
- package/dist/components/InsightsPanelUI.d.ts +0 -21
- package/dist/components/InsightsPanelUI.d.ts.map +0 -1
- package/dist/components/PaymentHistoryTimeline.d.ts +0 -34
- package/dist/components/PaymentHistoryTimeline.d.ts.map +0 -1
- package/dist/components/RelationshipManagerUI.d.ts +0 -60
- package/dist/components/RelationshipManagerUI.d.ts.map +0 -1
- package/dist/components/RoleManager.d.ts +0 -19
- package/dist/components/RoleManager.d.ts.map +0 -1
- package/dist/components/SplitCommissionBadge.d.ts +0 -18
- package/dist/components/SplitCommissionBadge.d.ts.map +0 -1
- package/dist/components/__tests__/Button.test.d.ts +0 -2
- package/dist/components/__tests__/Button.test.d.ts.map +0 -1
- package/dist/components/__tests__/Input.test.d.ts +0 -2
- package/dist/components/__tests__/Input.test.d.ts.map +0 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Spreadsheet } from './Spreadsheet';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Spreadsheet> = {
|
|
5
|
+
title: 'Components/Spreadsheet/Simple Test',
|
|
6
|
+
component: Spreadsheet,
|
|
7
|
+
parameters: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: {
|
|
10
|
+
component: 'Simple test story for Spreadsheet component debugging.',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default meta;
|
|
17
|
+
type Story = StoryObj<typeof Spreadsheet>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Minimal test - just render with defaults
|
|
21
|
+
*/
|
|
22
|
+
export const MinimalTest: Story = {
|
|
23
|
+
args: {
|
|
24
|
+
rows: 5,
|
|
25
|
+
columns: 3,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -249,3 +249,34 @@ export const Complete: Story = {
|
|
|
249
249
|
return <Tabs tabs={tabs} activeTab={activeTab} onChange={setActiveTab} variant="underline" />;
|
|
250
250
|
},
|
|
251
251
|
};
|
|
252
|
+
|
|
253
|
+
export const ControlledMode: Story = {
|
|
254
|
+
render: () => {
|
|
255
|
+
const [activeTab, setActiveTab] = useState('profile');
|
|
256
|
+
return (
|
|
257
|
+
<div>
|
|
258
|
+
<div style={{ marginBottom: '1rem', display: 'flex', gap: '0.5rem' }}>
|
|
259
|
+
<button
|
|
260
|
+
onClick={() => setActiveTab('profile')}
|
|
261
|
+
style={{ padding: '0.5rem 1rem', background: activeTab === 'profile' ? '#334155' : '#f1f5f9', color: activeTab === 'profile' ? 'white' : '#334155', border: 'none', borderRadius: '0.375rem', cursor: 'pointer' }}
|
|
262
|
+
>
|
|
263
|
+
Go to Profile
|
|
264
|
+
</button>
|
|
265
|
+
<button
|
|
266
|
+
onClick={() => setActiveTab('settings')}
|
|
267
|
+
style={{ padding: '0.5rem 1rem', background: activeTab === 'settings' ? '#334155' : '#f1f5f9', color: activeTab === 'settings' ? 'white' : '#334155', border: 'none', borderRadius: '0.375rem', cursor: 'pointer' }}
|
|
268
|
+
>
|
|
269
|
+
Go to Settings
|
|
270
|
+
</button>
|
|
271
|
+
<button
|
|
272
|
+
onClick={() => setActiveTab('notifications')}
|
|
273
|
+
style={{ padding: '0.5rem 1rem', background: activeTab === 'notifications' ? '#334155' : '#f1f5f9', color: activeTab === 'notifications' ? 'white' : '#334155', border: 'none', borderRadius: '0.375rem', cursor: 'pointer' }}
|
|
274
|
+
>
|
|
275
|
+
Go to Notifications
|
|
276
|
+
</button>
|
|
277
|
+
</div>
|
|
278
|
+
<Tabs tabs={basicTabs} activeTab={activeTab} onChange={setActiveTab} />
|
|
279
|
+
</div>
|
|
280
|
+
);
|
|
281
|
+
},
|
|
282
|
+
};
|
package/src/components/Tabs.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
2
|
|
|
3
3
|
export interface Tab {
|
|
4
4
|
id: string;
|
|
@@ -10,20 +10,44 @@ export interface Tab {
|
|
|
10
10
|
|
|
11
11
|
export interface TabsProps {
|
|
12
12
|
tabs: Tab[];
|
|
13
|
+
/** Controlled mode: Currently active tab ID */
|
|
14
|
+
activeTab?: string;
|
|
15
|
+
/** Uncontrolled mode: Initial tab ID (ignored if activeTab is provided) */
|
|
13
16
|
defaultTab?: string;
|
|
14
17
|
variant?: 'underline' | 'pill';
|
|
15
18
|
/** Orientation of tabs (default: 'horizontal') */
|
|
16
19
|
orientation?: 'horizontal' | 'vertical';
|
|
17
20
|
/** Size of tabs (default: 'md') */
|
|
18
21
|
size?: 'sm' | 'md' | 'lg';
|
|
22
|
+
/** Called when tab changes (required for controlled mode) */
|
|
19
23
|
onChange?: (tabId: string) => void;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
export default function Tabs({ tabs, defaultTab, variant = 'underline', orientation = 'horizontal', size = 'md', onChange }: TabsProps) {
|
|
23
|
-
const [
|
|
26
|
+
export default function Tabs({ tabs, activeTab: controlledActiveTab, defaultTab, variant = 'underline', orientation = 'horizontal', size = 'md', onChange }: TabsProps) {
|
|
27
|
+
const [internalActiveTab, setInternalActiveTab] = useState(defaultTab || tabs[0]?.id);
|
|
28
|
+
|
|
29
|
+
// Controlled mode: use activeTab prop, Uncontrolled mode: use internal state
|
|
30
|
+
const isControlled = controlledActiveTab !== undefined;
|
|
31
|
+
const activeTab = isControlled ? controlledActiveTab : internalActiveTab;
|
|
32
|
+
|
|
33
|
+
// Ensure the activeTab exists in the current tabs array
|
|
34
|
+
// This handles the case where tabs array reference changes at the same time as activeTab
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const tabExists = tabs.some(tab => tab.id === activeTab);
|
|
37
|
+
if (!tabExists && tabs.length > 0) {
|
|
38
|
+
// If the activeTab doesn't exist in the new tabs array, use the first tab
|
|
39
|
+
if (isControlled) {
|
|
40
|
+
onChange?.(tabs[0].id);
|
|
41
|
+
} else {
|
|
42
|
+
setInternalActiveTab(tabs[0].id);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}, [tabs, activeTab, isControlled, onChange]);
|
|
24
46
|
|
|
25
47
|
const handleTabChange = (tabId: string) => {
|
|
26
|
-
|
|
48
|
+
if (!isControlled) {
|
|
49
|
+
setInternalActiveTab(tabId);
|
|
50
|
+
}
|
|
27
51
|
onChange?.(tabId);
|
|
28
52
|
};
|
|
29
53
|
|
|
@@ -142,7 +142,7 @@ const TimePicker = forwardRef<TimePickerHandle, TimePickerProps>(({
|
|
|
142
142
|
|
|
143
143
|
// Format TimeValue to string
|
|
144
144
|
function formatTimeValue(tv: TimeValue, is12Hour: boolean, includeSeconds: boolean): string {
|
|
145
|
-
|
|
145
|
+
const hours = tv.hours;
|
|
146
146
|
|
|
147
147
|
if (is12Hour) {
|
|
148
148
|
const formatted = `${hours.toString().padStart(2, '0')}:${tv.minutes.toString().padStart(2, '0')}${includeSeconds ? ':' + tv.seconds.toString().padStart(2, '0') : ''} ${tv.period}`;
|
package/src/components/Toast.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
1
|
+
import React, { useEffect, useState, useCallback } from 'react';
|
|
2
2
|
import { X, CheckCircle, AlertCircle, Info, AlertTriangle } from 'lucide-react';
|
|
3
3
|
|
|
4
4
|
export type ToastType = 'success' | 'error' | 'warning' | 'info';
|
|
@@ -40,20 +40,20 @@ export default function Toast({ id, type, title, message, duration = 5000, onClo
|
|
|
40
40
|
const [isExiting, setIsExiting] = useState(false);
|
|
41
41
|
const styles = toastStyles[type];
|
|
42
42
|
|
|
43
|
+
const handleClose = useCallback(() => {
|
|
44
|
+
setIsExiting(true);
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
onClose(id);
|
|
47
|
+
}, 300); // Match animation duration
|
|
48
|
+
}, [id, onClose]);
|
|
49
|
+
|
|
43
50
|
useEffect(() => {
|
|
44
51
|
const timer = setTimeout(() => {
|
|
45
52
|
handleClose();
|
|
46
53
|
}, duration);
|
|
47
54
|
|
|
48
55
|
return () => clearTimeout(timer);
|
|
49
|
-
}, [duration]);
|
|
50
|
-
|
|
51
|
-
const handleClose = () => {
|
|
52
|
-
setIsExiting(true);
|
|
53
|
-
setTimeout(() => {
|
|
54
|
-
onClose(id);
|
|
55
|
-
}, 300); // Match animation duration
|
|
56
|
-
};
|
|
56
|
+
}, [duration, handleClose]);
|
|
57
57
|
|
|
58
58
|
return (
|
|
59
59
|
<div
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { render, screen
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import Input from '../Input';
|
|
4
4
|
|
|
@@ -19,8 +19,8 @@ describe('Input', () => {
|
|
|
19
19
|
expect(handleChange).toHaveBeenCalled();
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
it('shows
|
|
23
|
-
render(<Input label="Email" error="Invalid email" />);
|
|
22
|
+
it('shows validation message', () => {
|
|
23
|
+
render(<Input label="Email" validationState="error" validationMessage="Invalid email" />);
|
|
24
24
|
expect(screen.getByText('Invalid email')).toBeInTheDocument();
|
|
25
25
|
});
|
|
26
26
|
|
|
@@ -40,7 +40,7 @@ describe('Input', () => {
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
it('is read-only when readOnly prop is true', () => {
|
|
43
|
-
render(<Input label="Email" readOnly value="test@example.com" />);
|
|
43
|
+
render(<Input label="Email" readOnly value="test@example.com" onChange={() => {}} />);
|
|
44
44
|
const input = screen.getByLabelText('Email') as HTMLInputElement;
|
|
45
45
|
expect(input.readOnly).toBe(true);
|
|
46
46
|
});
|
|
@@ -55,39 +55,25 @@ describe('Input', () => {
|
|
|
55
55
|
expect(screen.getByText('.com')).toBeInTheDocument();
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
it('applies different sizes', () => {
|
|
59
|
-
const { rerender } = render(<Input label="Email" size="sm" />);
|
|
60
|
-
let input = screen.getByLabelText('Email');
|
|
61
|
-
expect(input).toHaveClass('text-sm');
|
|
62
|
-
|
|
63
|
-
rerender(<Input label="Email" size="lg" />);
|
|
64
|
-
input = screen.getByLabelText('Email');
|
|
65
|
-
expect(input).toHaveClass('text-lg');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
58
|
it('handles clear button click', async () => {
|
|
69
59
|
const user = userEvent.setup();
|
|
70
60
|
const handleClear = jest.fn();
|
|
61
|
+
const handleChange = jest.fn();
|
|
71
62
|
|
|
72
|
-
render(<Input label="Search" clearable onClear={handleClear} value="test" />);
|
|
63
|
+
render(<Input label="Search" clearable onClear={handleClear} value="test" onChange={handleChange} />);
|
|
73
64
|
|
|
74
|
-
// Find
|
|
75
|
-
const clearButton = screen.getByLabelText('Clear');
|
|
65
|
+
// Find clear button by aria-label
|
|
66
|
+
const clearButton = screen.getByLabelText('Clear input');
|
|
76
67
|
await user.click(clearButton);
|
|
77
68
|
|
|
78
69
|
expect(handleClear).toHaveBeenCalled();
|
|
79
70
|
});
|
|
80
71
|
|
|
81
|
-
it('
|
|
82
|
-
render(<Input label="Email"
|
|
83
|
-
// Loading indicator should be rendered
|
|
84
|
-
expect(screen.getByLabelText('Email')).toBeInTheDocument();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('applies error styling when error is present', () => {
|
|
88
|
-
render(<Input label="Email" error="Invalid" />);
|
|
72
|
+
it('applies error styling when validation state is error', () => {
|
|
73
|
+
render(<Input label="Email" validationState="error" validationMessage="Invalid" />);
|
|
89
74
|
const input = screen.getByLabelText('Email');
|
|
90
|
-
|
|
75
|
+
// Check for error border class
|
|
76
|
+
expect(input).toHaveClass('border-error-400');
|
|
91
77
|
});
|
|
92
78
|
|
|
93
79
|
it('supports different input types', () => {
|
|
@@ -99,4 +85,14 @@ describe('Input', () => {
|
|
|
99
85
|
input = screen.getByLabelText('Password') as HTMLInputElement;
|
|
100
86
|
expect(input.type).toBe('password');
|
|
101
87
|
});
|
|
88
|
+
|
|
89
|
+
it('shows character count when enabled', () => {
|
|
90
|
+
render(<Input label="Bio" showCount maxLength={100} value="Hello" onChange={() => {}} />);
|
|
91
|
+
expect(screen.getByText(/5/)).toBeInTheDocument();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('shows password toggle button for password input', () => {
|
|
95
|
+
render(<Input label="Password" type="password" showPasswordToggle />);
|
|
96
|
+
expect(screen.getByLabelText('Show password')).toBeInTheDocument();
|
|
97
|
+
});
|
|
102
98
|
});
|
package/src/components/index.ts
CHANGED
|
@@ -283,13 +283,17 @@ export type { NotificationIndicatorProps } from './NotificationIndicator';
|
|
|
283
283
|
|
|
284
284
|
// Data Table
|
|
285
285
|
export { default as DataTable } from './DataTable';
|
|
286
|
-
export type {
|
|
287
|
-
DataTableColumn,
|
|
286
|
+
export type {
|
|
287
|
+
DataTableColumn,
|
|
288
288
|
DataTableAction,
|
|
289
289
|
ExpandedRowConfig,
|
|
290
290
|
ExpansionMode
|
|
291
291
|
} from './DataTable';
|
|
292
292
|
|
|
293
|
+
// Spreadsheet
|
|
294
|
+
export { Spreadsheet, SpreadsheetReport } from './Spreadsheet';
|
|
295
|
+
export type { SpreadsheetProps, SpreadsheetCell, Matrix, CellBase } from './Spreadsheet';
|
|
296
|
+
|
|
293
297
|
export { default as ExpandedRowEditForm } from './ExpandedRowEditForm';
|
|
294
298
|
export type {
|
|
295
299
|
ExpandedRowEditFormProps,
|
|
@@ -305,6 +309,8 @@ export type {
|
|
|
305
309
|
// Display Components
|
|
306
310
|
export { default as CurrencyDisplay } from './CurrencyDisplay';
|
|
307
311
|
export type { CurrencyDisplayProps } from './CurrencyDisplay';
|
|
312
|
+
export { default as CurrencyInput } from './CurrencyInput';
|
|
313
|
+
export type { CurrencyInputProps } from './CurrencyInput';
|
|
308
314
|
|
|
309
315
|
export { default as DateDisplay } from './DateDisplay';
|
|
310
316
|
export type { DateDisplayProps } from './DateDisplay';
|
|
@@ -405,6 +411,9 @@ export {
|
|
|
405
411
|
} from '../utils/tableEnhancements';
|
|
406
412
|
export type { ColumnResize, ColumnOrder } from '../utils/tableEnhancements';
|
|
407
413
|
|
|
414
|
+
export { exportToExcel, exportDataTableToExcel, createMultiSheetExcel } from '../utils/excelExport';
|
|
415
|
+
export type { ExcelColumn, ExportToExcelOptions, DataTableExportOptions, MultiSheetExcelOptions } from '../utils/excelExport';
|
|
416
|
+
|
|
408
417
|
// Hooks
|
|
409
418
|
export { useColumnResize, useColumnReorder } from '../hooks/useTableEnhancements';
|
|
410
419
|
export type { UseColumnResizeOptions, UseColumnReorderOptions } from '../hooks/useTableEnhancements';
|
package/src/styles/index.css
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
|
2
2
|
|
|
3
|
+
/* Component styles */
|
|
4
|
+
@import '../components/Spreadsheet.css';
|
|
5
|
+
|
|
3
6
|
@tailwind base;
|
|
4
7
|
@tailwind components;
|
|
5
8
|
@tailwind utilities;
|
|
@@ -169,16 +172,53 @@
|
|
|
169
172
|
@apply text-error-600;
|
|
170
173
|
}
|
|
171
174
|
|
|
172
|
-
/* Notebook Page Container - Creates bounded page effect
|
|
175
|
+
/* Notebook Page Container - Creates bounded page effect
|
|
176
|
+
NOTE: This class is deprecated - Page component now handles responsive layout via props
|
|
177
|
+
Keeping for backward compatibility only */
|
|
173
178
|
.notebook-page {
|
|
174
179
|
@apply bg-white bg-subtle-grain rounded-sm shadow-lg border-l-4 border-paper-300;
|
|
175
180
|
max-width: 1400px;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
181
|
+
/* Responsive margins - fixed left/top, responsive right/bottom */
|
|
182
|
+
margin-top: 1rem;
|
|
183
|
+
margin-left: 1rem;
|
|
184
|
+
margin-right: 1rem;
|
|
185
|
+
margin-bottom: 1rem;
|
|
186
|
+
/* Responsive padding - fixed left/top, responsive right/bottom */
|
|
187
|
+
padding-top: 3rem;
|
|
188
|
+
padding-left: 5rem; /* Extra left padding for binding margin */
|
|
189
|
+
padding-right: 1rem;
|
|
190
|
+
padding-bottom: 1rem;
|
|
191
|
+
min-height: calc(100vh - 2rem);
|
|
179
192
|
position: relative;
|
|
180
193
|
}
|
|
181
194
|
|
|
195
|
+
/* Responsive padding/margin increases on larger screens */
|
|
196
|
+
@media (min-width: 640px) {
|
|
197
|
+
.notebook-page {
|
|
198
|
+
margin-right: 1.5rem;
|
|
199
|
+
margin-bottom: 2rem;
|
|
200
|
+
padding-right: 2rem;
|
|
201
|
+
padding-bottom: 2rem;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@media (min-width: 768px) {
|
|
206
|
+
.notebook-page {
|
|
207
|
+
margin-right: 2rem;
|
|
208
|
+
margin-bottom: 2rem;
|
|
209
|
+
padding-right: 3rem;
|
|
210
|
+
padding-bottom: 3rem;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@media (min-width: 1024px) {
|
|
215
|
+
.notebook-page {
|
|
216
|
+
margin-right: auto; /* Center on large screens */
|
|
217
|
+
padding-right: 4rem;
|
|
218
|
+
padding-bottom: 4rem;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
182
222
|
/* Notebook binding effect on sidebar */
|
|
183
223
|
.notebook-binding {
|
|
184
224
|
position: relative;
|
|
@@ -427,8 +467,6 @@
|
|
|
427
467
|
|
|
428
468
|
.table-stable tbody tr td {
|
|
429
469
|
vertical-align: top;
|
|
430
|
-
overflow: hidden;
|
|
431
|
-
text-overflow: ellipsis;
|
|
432
470
|
}
|
|
433
471
|
|
|
434
472
|
/* Smooth loading overlay */
|