@industry-theme/markdown-panels 0.2.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,466 @@
1
+ # Industry-Themed Markdown Panels
2
+
3
+ A panel extension for rendering markdown documents with industry theming support, built using `@principal-ade/panel-framework-core`.
4
+
5
+ This panel demonstrates how to integrate `themed-markdown` with the panel framework, following patterns from the desktop-app's `MarkdownRenderingPanel` and `GitHubReadmePanel`.
6
+
7
+ ## Features
8
+
9
+ - **Themed Markdown Rendering**: Uses `themed-markdown` with `@a24z/industry-theme` for consistent, professional styling
10
+ - **Multiple View Modes**: Switch between document view (all content) and slide view (paginated)
11
+ - **Font Size Controls**: Adjustable font scaling from 50% to 300%
12
+ - **Slide Navigation**: Navigate through markdown slides with previous/next controls
13
+ - **Panel Framework Integration**: Full integration with context, actions, and events
14
+ - **Responsive Design**: Adapts to different panel sizes and layouts
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install
20
+ ```
21
+
22
+ ## Development
23
+
24
+ ```bash
25
+ # Development mode (watch for changes)
26
+ npm run dev
27
+
28
+ # Build for production
29
+ npm run build
30
+
31
+ # Type checking
32
+ npm run typecheck
33
+
34
+ # Linting
35
+ npm run lint
36
+
37
+ # Storybook
38
+ npm run storybook
39
+ ```
40
+
41
+ ## Project Structure
42
+
43
+ ```
44
+ industry-themed-markdown-panels/
45
+ ├── src/
46
+ │ ├── panels/
47
+ │ │ └── MarkdownPanel.tsx # Main markdown panel component
48
+ │ ├── types/
49
+ │ │ └── index.ts # TypeScript type definitions
50
+ │ ├── mocks/
51
+ │ │ └── panelContext.tsx # Mock providers for Storybook
52
+ │ └── index.tsx # Panel registration
53
+ ├── dist/
54
+ │ ├── panels.bundle.js # Built panel bundle
55
+ │ └── index.d.ts # TypeScript declarations
56
+ ├── package.json
57
+ ├── vite.config.ts
58
+ └── README.md
59
+ ```
60
+
61
+ ## Usage
62
+
63
+ The panel exports a single `MarkdownPanel` that demonstrates:
64
+
65
+ 1. How to use `DocumentView` from `themed-markdown`
66
+ 2. How to integrate with `@a24z/industry-theme`
67
+ 3. How to parse markdown into slides using `parseMarkdownIntoPresentation`
68
+ 4. How to build interactive panel controls
69
+ 5. How to access panel context for repository information
70
+
71
+ ## Panel Definition
72
+
73
+ ```typescript
74
+ {
75
+ id: 'principal-ade.markdown-viewer',
76
+ name: 'Markdown Viewer',
77
+ icon: '📄',
78
+ description: 'Themed markdown rendering panel with document and slide views',
79
+ component: MarkdownPanel
80
+ }
81
+ ```
82
+
83
+ ## Panel Component API
84
+
85
+ ### PanelComponentProps
86
+
87
+ Every panel component receives these props:
88
+
89
+ ```typescript
90
+ interface PanelComponentProps {
91
+ // Access to shared data and state
92
+ context: PanelContextValue;
93
+
94
+ // Actions for host interaction
95
+ actions: PanelActions;
96
+
97
+ // Event system for inter-panel communication
98
+ events: PanelEventEmitter;
99
+ }
100
+ ```
101
+
102
+ ### Context
103
+
104
+ Access repository data and state:
105
+
106
+ ```tsx
107
+ const { context } = props;
108
+
109
+ // Repository information
110
+ context.repositoryPath // Current repository path
111
+ context.repository // Repository metadata
112
+
113
+ // Data slices
114
+ context.gitStatus // Git status information
115
+ context.fileTree // File tree structure
116
+ context.markdownFiles // Markdown files list
117
+
118
+ // State management
119
+ context.loading // Loading state
120
+ context.refresh() // Refresh data
121
+ context.hasSlice('git') // Check slice availability
122
+ ```
123
+
124
+ ### Actions
125
+
126
+ Interact with the host application:
127
+
128
+ ```tsx
129
+ const { actions } = props;
130
+
131
+ // File operations
132
+ actions.openFile?.('path/to/file.ts');
133
+ actions.openGitDiff?.('path/to/file.ts', 'unstaged');
134
+
135
+ // Navigation
136
+ actions.navigateToPanel?.('panel-id');
137
+
138
+ // Notifications
139
+ actions.notifyPanels?.(event);
140
+ ```
141
+
142
+ ### Events
143
+
144
+ Subscribe to and emit panel events:
145
+
146
+ ```tsx
147
+ const { events } = props;
148
+
149
+ // Subscribe to events
150
+ useEffect(() => {
151
+ const unsubscribe = events.on('file:opened', (event) => {
152
+ console.log('File opened:', event.payload);
153
+ });
154
+
155
+ return unsubscribe; // Cleanup
156
+ }, [events]);
157
+
158
+ // Emit events
159
+ events.emit({
160
+ type: 'custom:event',
161
+ source: 'my-panel',
162
+ timestamp: Date.now(),
163
+ payload: { data: 'value' },
164
+ });
165
+ ```
166
+
167
+ ## Panel Definition
168
+
169
+ Each panel must be defined with metadata:
170
+
171
+ ```typescript
172
+ interface PanelDefinition {
173
+ id: string; // Unique ID (e.g., 'org.panel-name')
174
+ name: string; // Display name
175
+ icon?: string; // Icon (emoji or URL)
176
+ version?: string; // Version (defaults to package.json)
177
+ author?: string; // Author (defaults to package.json)
178
+ description?: string; // Short description
179
+ component: React.FC; // The panel component
180
+
181
+ // Optional lifecycle hooks
182
+ onMount?: (context) => void | Promise<void>;
183
+ onUnmount?: (context) => void | Promise<void>;
184
+ onDataChange?: (slice, data) => void;
185
+ }
186
+ ```
187
+
188
+ ## Lifecycle Hooks
189
+
190
+ ### Per-Panel Hooks
191
+
192
+ Called for individual panels:
193
+
194
+ ```typescript
195
+ {
196
+ id: 'my-panel',
197
+ component: MyPanel,
198
+
199
+ onMount: async (context) => {
200
+ console.log('Panel mounted');
201
+ if (context.hasSlice('git')) {
202
+ await context.refresh();
203
+ }
204
+ },
205
+
206
+ onUnmount: async (context) => {
207
+ console.log('Panel unmounting');
208
+ // Cleanup logic
209
+ },
210
+
211
+ onDataChange: (slice, data) => {
212
+ console.log(`Data changed: ${slice}`, data);
213
+ },
214
+ }
215
+ ```
216
+
217
+ ### Package-Level Hooks
218
+
219
+ Called once for the entire package:
220
+
221
+ ```typescript
222
+ export const onPackageLoad = async () => {
223
+ console.log('Package loaded');
224
+ // Initialize shared resources
225
+ };
226
+
227
+ export const onPackageUnload = async () => {
228
+ console.log('Package unloading');
229
+ // Cleanup shared resources
230
+ };
231
+ ```
232
+
233
+ ## Building and Publishing
234
+
235
+ ### Build Configuration
236
+
237
+ The build process (via Vite) automatically:
238
+ - Externalizes React and ReactDOM (provided by host)
239
+ - Bundles all other dependencies
240
+ - Generates TypeScript declarations
241
+ - Creates source maps
242
+ - Outputs to `dist/panels.bundle.js`
243
+
244
+ ### Local Testing
245
+
246
+ Link your panel locally for testing:
247
+
248
+ ```bash
249
+ # In your panel directory
250
+ bun run build
251
+ bun link
252
+
253
+ # In your host application
254
+ bun link @your-org/your-panel-name
255
+ ```
256
+
257
+ ### Publishing to NPM
258
+
259
+ ```bash
260
+ # Build the package
261
+ bun run build
262
+
263
+ # Verify the output
264
+ ls -la dist/
265
+
266
+ # Publish to NPM
267
+ npm publish --access public
268
+ ```
269
+
270
+ ### Installing in Host Application
271
+
272
+ ```bash
273
+ # In the host application
274
+ npm install @your-org/your-panel-name
275
+ ```
276
+
277
+ The host application will automatically discover your panel by the `panel-extension` keyword in `package.json`.
278
+
279
+ ## Best Practices
280
+
281
+ ### 1. Namespaced Panel IDs
282
+
283
+ Use reverse domain notation for panel IDs:
284
+
285
+ ```typescript
286
+ id: 'com.company.feature-panel' // ✅ Good
287
+ id: 'my-panel' // ❌ Bad (collision risk)
288
+ ```
289
+
290
+ ### 2. Error Handling
291
+
292
+ Always handle errors gracefully:
293
+
294
+ ```tsx
295
+ const [error, setError] = useState(null);
296
+
297
+ useEffect(() => {
298
+ const loadData = async () => {
299
+ try {
300
+ if (!context.hasSlice('git')) {
301
+ throw new Error('Git data not available');
302
+ }
303
+ // Use data...
304
+ } catch (err) {
305
+ setError(err);
306
+ }
307
+ };
308
+ loadData();
309
+ }, [context]);
310
+
311
+ if (error) {
312
+ return <div>Error: {error.message}</div>;
313
+ }
314
+ ```
315
+
316
+ ### 3. Loading States
317
+
318
+ Show loading indicators:
319
+
320
+ ```tsx
321
+ if (context.loading || context.isSliceLoading('git')) {
322
+ return <div>Loading...</div>;
323
+ }
324
+ ```
325
+
326
+ ### 4. Cleanup Subscriptions
327
+
328
+ Always unsubscribe from events:
329
+
330
+ ```tsx
331
+ useEffect(() => {
332
+ const unsubscribe = events.on('event:type', handler);
333
+ return unsubscribe; // Cleanup on unmount
334
+ }, [events]);
335
+ ```
336
+
337
+ ### 5. Type Safety
338
+
339
+ Use provided types for type safety:
340
+
341
+ ```tsx
342
+ import type { PanelComponentProps, GitStatus } from './types';
343
+
344
+ const MyPanel: React.FC<PanelComponentProps> = ({ context }) => {
345
+ const gitStatus: GitStatus = context.gitStatus;
346
+ // ...
347
+ };
348
+ ```
349
+
350
+ ## Available Data Slices
351
+
352
+ Panels can access these data slices from the host:
353
+
354
+ | Slice | Type | Description |
355
+ |-------|------|-------------|
356
+ | `git` | `GitStatus` | Git repository status |
357
+ | `markdown` | `MarkdownFile[]` | Markdown files in repository |
358
+ | `fileTree` | `FileTree` | File system tree structure |
359
+ | `packages` | `PackageLayer[]` | Package dependencies |
360
+ | `quality` | `QualityMetrics` | Code quality metrics |
361
+
362
+ Check availability before use:
363
+
364
+ ```tsx
365
+ if (context.hasSlice('git') && !context.isSliceLoading('git')) {
366
+ // Use git data
367
+ }
368
+ ```
369
+
370
+ ## Event Types
371
+
372
+ Standard panel events:
373
+
374
+ | Event | Description | Payload |
375
+ |-------|-------------|---------|
376
+ | `file:opened` | File was opened | `{ filePath: string }` |
377
+ | `file:saved` | File was saved | `{ filePath: string }` |
378
+ | `file:deleted` | File was deleted | `{ filePath: string }` |
379
+ | `git:status-changed` | Git status changed | `GitStatus` |
380
+ | `git:commit` | Git commit made | `{ hash: string }` |
381
+ | `git:branch-changed` | Branch changed | `{ branch: string }` |
382
+ | `panel:focus` | Panel gained focus | `{ panelId: string }` |
383
+ | `panel:blur` | Panel lost focus | `{ panelId: string }` |
384
+ | `data:refresh` | Data was refreshed | `{ slices: string[] }` |
385
+
386
+ ## Dependencies
387
+
388
+ ### Peer Dependencies (Required)
389
+
390
+ These are provided by the host application:
391
+
392
+ - `react` >= 18.0.0
393
+ - `react-dom` >= 18.0.0
394
+
395
+ ### Optional Peer Dependencies
396
+
397
+ - `@principal-ade/panel-framework-core` - For advanced panel features
398
+
399
+ ### Bundled Dependencies
400
+
401
+ Include any libraries unique to your panel:
402
+
403
+ ```json
404
+ {
405
+ "dependencies": {
406
+ "lodash": "^4.17.21",
407
+ "date-fns": "^2.29.0",
408
+ "your-custom-lib": "^1.0.0"
409
+ }
410
+ }
411
+ ```
412
+
413
+ These will be bundled into your panel output.
414
+
415
+ ## Troubleshooting
416
+
417
+ ### Panel Not Discovered
418
+
419
+ Ensure `package.json` has:
420
+ ```json
421
+ {
422
+ "keywords": ["panel-extension"],
423
+ "main": "dist/panels.bundle.js"
424
+ }
425
+ ```
426
+
427
+ ### Build Errors
428
+
429
+ Check that peer dependencies are externalized in `vite.config.ts`:
430
+ ```typescript
431
+ external: ['react', 'react-dom']
432
+ ```
433
+
434
+ ### Type Errors
435
+
436
+ Ensure TypeScript can find types:
437
+ ```bash
438
+ bun run typecheck
439
+ ```
440
+
441
+ ### Runtime Errors
442
+
443
+ Check browser console and ensure:
444
+ - Panel ID is unique
445
+ - Required exports are present (`panels` array)
446
+ - Component is a valid React component
447
+
448
+ ## Resources
449
+
450
+ - [Panel Extension Store Specification V2](https://github.com/principal-ade/panel-framework/blob/main/PANEL_EXTENSION_STORE_SPECIFICATION_V2.md)
451
+ - [Panel Framework Core](https://github.com/principal-ade/panel-framework-core)
452
+ - [Example Implementations](https://github.com/principal-ade/example-panels)
453
+
454
+ ## License
455
+
456
+ MIT © Your Name
457
+
458
+ ## Contributing
459
+
460
+ Contributions welcome! Please read the contributing guidelines first.
461
+
462
+ ## Support
463
+
464
+ For issues and questions:
465
+ - [GitHub Issues](https://github.com/your-org/your-panel-name/issues)
466
+ - [Discussions](https://github.com/your-org/your-panel-name/discussions)
@@ -0,0 +1,17 @@
1
+ import type { PanelDefinition } from './types';
2
+ /**
3
+ * Export array of panel definitions.
4
+ * This is the required export for panel extensions.
5
+ */
6
+ export declare const panels: PanelDefinition[];
7
+ /**
8
+ * Optional: Called once when the entire package is loaded.
9
+ * Use this for package-level initialization.
10
+ */
11
+ export declare const onPackageLoad: () => Promise<void>;
12
+ /**
13
+ * Optional: Called once when the package is unloaded.
14
+ * Use this for package-level cleanup.
15
+ */
16
+ export declare const onPackageUnload: () => Promise<void>;
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAC;AAElE;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,eAAe,EAwBnC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,qBAEzB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,qBAE3B,CAAC"}
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import type { PanelComponentProps, PanelContextValue, PanelActions, PanelEventEmitter, GitStatus } from '../types';
3
+ /**
4
+ * Mock Git Status for Storybook
5
+ */
6
+ export declare const mockGitStatus: GitStatus;
7
+ /**
8
+ * Mock Panel Context for Storybook
9
+ */
10
+ export declare const createMockContext: (overrides?: Partial<PanelContextValue>) => PanelContextValue;
11
+ /**
12
+ * Mock Panel Actions for Storybook
13
+ */
14
+ export declare const createMockActions: (overrides?: Partial<PanelActions>) => PanelActions;
15
+ /**
16
+ * Mock Event Emitter for Storybook
17
+ */
18
+ export declare const createMockEvents: () => PanelEventEmitter;
19
+ /**
20
+ * Create complete mock panel context props
21
+ * Utility for creating all panel props at once
22
+ */
23
+ export declare const createMockPanelContext: (contextOverrides?: Partial<PanelContextValue>, actionsOverrides?: Partial<PanelActions>) => PanelComponentProps;
24
+ /**
25
+ * Mock Panel Props Provider
26
+ * Wraps components with mock context for Storybook
27
+ */
28
+ export declare const MockPanelProvider: React.FC<{
29
+ children: (props: PanelComponentProps) => React.ReactNode;
30
+ contextOverrides?: Partial<PanelContextValue>;
31
+ actionsOverrides?: Partial<PanelActions>;
32
+ }>;
33
+ //# sourceMappingURL=panelContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panelContext.d.ts","sourceRoot":"","sources":["../../src/mocks/panelContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EAEjB,SAAS,EACV,MAAM,UAAU,CAAC;AAElB;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,SAK3B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAC5B,YAAY,OAAO,CAAC,iBAAiB,CAAC,KACrC,iBAoDF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAC5B,YAAY,OAAO,CAAC,YAAY,CAAC,KAChC,YAcD,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,gBAAgB,QAAO,iBA6BnC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GACjC,mBAAmB,OAAO,CAAC,iBAAiB,CAAC,EAC7C,mBAAmB,OAAO,CAAC,YAAY,CAAC,KACvC,mBAMF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC;IACvC,QAAQ,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC1D,gBAAgB,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC9C,gBAAgB,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;CAC1C,CAGA,CAAC"}
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import 'themed-markdown/dist/index.css';
3
+ import type { PanelComponentProps } from '../types';
4
+ /**
5
+ * MarkdownPanel - A panel for rendering markdown documents with industry theming
6
+ *
7
+ * This panel integrates with the panel framework to:
8
+ * - Listen to file:opened events
9
+ * - Read content from the active-file context slice
10
+ * - Display markdown with themed rendering using DocumentView
11
+ * - Support view mode switching between document and slides
12
+ * - Provide font size controls and slide navigation
13
+ */
14
+ export declare const MarkdownPanel: React.FC<PanelComponentProps>;
15
+ //# sourceMappingURL=MarkdownPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownPanel.d.ts","sourceRoot":"","sources":["../../src/panels/MarkdownPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAQ5D,OAAO,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,mBAAmB,EAAmB,MAAM,UAAU,CAAC;AAUrE;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA6UvD,CAAC"}
@@ -0,0 +1 @@
1
+ pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-keyword,.hljs-formula{color:#c678dd}.hljs-section,.hljs-name,.hljs-selector-tag,.hljs-deletion,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-string,.hljs-regexp,.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string{color:#98c379}.hljs-attr,.hljs-variable,.hljs-template-variable,.hljs-type,.hljs-selector-class,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-number{color:#d19a66}.hljs-symbol,.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-title.class_,.hljs-class .hljs-title{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}.animated-resizable-layout{width:100%;height:100%;position:relative;background-color:var(--panel-background)}.hybrid-panel{overflow:auto;height:100%;background-color:var(--panel-background)}.panel-content-wrapper{width:100%;height:100%;overflow:auto}.resize-handle{width:8px;background-color:var(--panel-handle);display:flex;justify-content:center;align-items:center;cursor:col-resize;position:relative;transition:background-color .2s,opacity .3s,width .3s}.resize-handle.collapsed{opacity:0;pointer-events:none}.resize-handle:hover{background-color:var(--panel-handle-hover)}.resize-handle:active{background-color:var(--panel-handle-active)}.handle-bar{height:100%;width:100%;display:flex;align-items:center;justify-content:center;position:relative}.collapse-toggle{position:absolute;background:var(--panel-button-bg);border:1px solid var(--panel-button-border);padding:4px 8px;cursor:pointer;color:var(--panel-button-icon);font-size:14px;outline:none;display:flex;align-items:center;justify-content:center;border-radius:4px;z-index:10;transition:all .2s;box-shadow:0 2px 4px #0000001a}.collapse-toggle:hover:not(:disabled){background-color:var(--panel-button-hover);box-shadow:0 2px 6px #00000026}.collapse-toggle:active:not(:disabled){opacity:.8}.three-panel-layout{height:100%;width:100%;display:flex;flex-direction:column;position:relative;background-color:var(--panel-background)}.three-panel-item{display:flex;flex-direction:column;overflow:hidden;position:relative;background-color:var(--panel-background)}.three-panel-item.collapsible-panel{will-change:flex}.three-panel-item.collapsible-panel.animating{pointer-events:none}.three-panel-item.collapsible-panel.collapsed{flex:0!important;min-width:0!important}.three-panel-item.middle-panel{flex:1;min-width:200px}.panel-content-wrapper{flex:1;overflow:auto;will-change:opacity}.resize-handle{position:relative;display:flex;align-items:center;justify-content:center;width:1px!important;cursor:col-resize;background:var(--panel-border);overflow:visible!important}.resize-handle:before{content:"";position:absolute;top:0;bottom:0;left:-10px;right:-10px;background:transparent}.resize-handle:after{content:"";position:absolute;top:0;bottom:0;left:-10px;right:-10px;background:var(--panel-handle);opacity:0;transition:opacity .2s ease;z-index:-1}.resize-handle:hover:after{opacity:1}.resize-handle:hover{background:var(--panel-handle-hover)}.resize-handle:active:after{opacity:1;background:var(--panel-handle-active)}.resize-handle:active{background:var(--panel-handle-active)}.resize-handle.collapsed{pointer-events:none;width:0!important;overflow:hidden}.resize-handle.left-handle.collapsed{margin-right:-1px}.resize-handle.right-handle.collapsed{margin-left:-1px}.handle-bar{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center}.collapse-toggle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:20px;height:40px;background:var(--panel-button-bg);border:1px solid var(--panel-button-border);border-radius:4px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:12px;color:var(--panel-button-icon);transition:all .2s ease;z-index:10;padding:0;line-height:1}.collapse-toggle:hover{background:var(--panel-button-hover)}.collapse-toggle:active{opacity:.8}.collapse-toggle:disabled{opacity:.5;cursor:not-allowed}@media(max-width:768px){.resize-handle:before{left:-8px;right:-8px}.resize-handle:after{left:-8px;right:-8px}.collapse-toggle{width:24px;height:48px;font-size:14px}}