@akinon/ui-shell-dev 1.0.0 → 1.1.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.
@@ -0,0 +1 @@
1
+ .akinon-dropdown-menu-item-selected{font-weight:700!important}.akinon-method-modal{font-family:var(--token-fontFamily)}.akinon-method-modal-content{background-color:var(--token-colorBgElevated)!important}.akinon-method-modal-confirm-title{color:var(--color-neutral-50)!important}.akinon-method-modal-confirm-content{color:var(--color-neutral-100)!important;font-weight:300!important}.ant-notification-notice{padding:21px 70px 21px 24px!important;border-radius:5px;background-color:var(--color-ebonyClay-500)}.ant-notification-notice .ant-notification-notice-icon{right:24.5px!important}.ant-notification-notice .ant-notification-notice-message{color:var(--color-neutral-50)!important;font-size:var(--token-fontSize)!important;line-height:1.4285714285714286!important;margin:0!important;font-family:var(--token-fontFamily)}.ant-notification-notice-success{border:1px solid var(--color-green-954)}.ant-notification-notice-success .ant-notification-notice-icon svg{color:var(--color-green-954)}.ant-notification-notice-info{border:1px solid var(--color-orange-954)}.ant-notification-notice-info .ant-notification-notice-icon svg{color:var(--color-orange-954)}.ant-notification-notice-error{border:1px solid var(--color-red-425)}.ant-notification-notice-error .ant-notification-notice-icon svg{color:var(--color-red-425)}.akinon-table-wrapper{font-weight:500;line-height:normal}.akinon-table-wrapper .akinon-table-tbody tr:last-child td{border-bottom:none}.akinon-table-wrapper .akinon-table-thead>tr>th{border-bottom:none}.akinon-table-wrapper .akinon-table-thead .akinon-table-selection{display:none}.akinon-table-wrapper .akinon-checkbox-wrapper{margin-right:20px}.akinon-upload.akinon-upload-select{cursor:pointer;flex:1;width:100%;padding:10px 20px;font-weight:600;background-color:var(--color-ebonyClay-475);border-radius:5px;background-image:url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' stroke='black' stroke-width='4' stroke-dasharray='6%2c 12' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e")}.akinon-upload.akinon-upload-select:not(:has(.akinon-upload-list-item)){max-width:300px}.akinon-upload-wrapper .akinon-upload-list:not(.akinon-upload-list-picture-card){line-height:0;max-width:300px}.akinon-upload-wrapper .akinon-upload-list:not(.akinon-upload-list-picture-card) .akinon-upload-list-item{border-radius:5px;height:unset;padding:10px 20px;background-color:var(--color-ebonyClay-550);font-size:14px;font-weight:600;gap:10px;align-items:center}.akinon-upload-wrapper .akinon-upload-list:not(.akinon-upload-list-picture-card) .akinon-upload-list-item .akinon-upload-list-item-name{white-space:normal;word-break:break-all}.akinon-upload-wrapper.akinon-upload-picture-card-wrapper .akinon-upload-list.akinon-upload-list-picture-card .akinon-upload-list-item{border-radius:0;border:2px solid var(--color-neutral-350)}.akinon-upload-wrapper.akinon-upload-picture-card-wrapper .akinon-upload-list.akinon-upload-list-picture-card .akinon-upload-list-item-error{border-color:var(--color-red-500)}.akinon-upload-wrapper.akinon-upload-picture-card-wrapper .akinon-upload-list.akinon-upload-list-picture-card .akinon-upload-list-item:before{display:none}.akinon-upload-wrapper.akinon-upload-picture-card-wrapper .akinon-upload-list.akinon-upload-list-picture-card .akinon-upload-list-item-actions{cursor:pointer;width:fit-content;inset:auto 6px 6px auto;background-color:#ffffff80;display:flex;align-items:center;justify-content:center;border-radius:100%;height:18px;width:18px;font-size:12px}.akinon-upload-wrapper .akinon-upload-list .akinon-upload-list-item .akinon-upload-list-item-actions .akinon-upload-list-item-action{opacity:1}.akinon-layout-footer{text-align:center}.akinon-layout-header{padding-inline:1rem;height:3.5rem;border-bottom:1px solid var(--color-gray-900);display:flex;align-items:center;justify-content:space-between;line-height:unset;position:sticky;top:0;z-index:999}.akinon-layout-sider{position:sticky;top:0;height:100svh;overflow-y:auto}.akinon-layout-sider.left{inset-inline-start:0}.akinon-layout-sider.right{inset-inline-end:0}.akinon-layout-sider .logo-container{display:flex;flex-direction:row;align-items:center;height:3.5rem;gap:.8rem;background-color:var(--color-ebonyClay-600);padding-inline:1.5rem}.akinon-layout-sider .logo-container.collapsed{justify-content:center;padding-inline:1rem}.akinon-layout-sider .logo-container.collapsed .brand-image{display:none}.log-messages{display:flex;flex-direction:column;padding:.75rem}.log-messages .akinon-space-item{width:100%}.log-messages-item{border-radius:10px;border:1px solid #fff;padding:.75rem;display:flex;flex-direction:column;margin-bottom:.25rem}.log-messages-item>div{width:100%}.flex{display:flex}.items-center{align-items:center}.mr-2{margin-right:.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-8{padding-top:2rem;padding-bottom:2rem}
package/dist/index.html CHANGED
@@ -24,8 +24,8 @@
24
24
  box-sizing: border-box;
25
25
  }
26
26
  </style>
27
- <script type="module" crossorigin src="/assets/index-DUUd718F.js"></script>
28
- <link rel="stylesheet" crossorigin href="/assets/index-CuIRnKVE.css">
27
+ <script type="module" crossorigin src="/assets/index-cI-pE1VB.js"></script>
28
+ <link rel="stylesheet" crossorigin href="/assets/index-tRJwBFdH.css">
29
29
  </head>
30
30
  <body>
31
31
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/ui-shell-dev",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "description": "Development shell application for Akinon UI Protocol plugins",
6
6
  "type": "module",
@@ -14,11 +14,7 @@
14
14
  "tsconfig.json"
15
15
  ],
16
16
  "dependencies": {
17
- "@akinon/ui-system": "^1.0.0",
18
- "@akinon/ui-layout": "^1.0.0",
19
- "@akinon/ui-menu": "^1.0.0",
20
- "@akinon/ui-breadcrumb": "^1.0.0",
21
- "@akinon/ui-button": "^1.0.0",
17
+ "@akinon/ui-react": "^1.1.0",
22
18
  "@akinon/icons": "^1.0.0",
23
19
  "@akinon/fonts-jost-variable": "^1.0.0",
24
20
  "react": "^18.2.0",
@@ -28,13 +24,13 @@
28
24
  "vite": "^5.2.0"
29
25
  },
30
26
  "devDependencies": {
31
- "@akinon/typescript-config": "^1.0.0",
32
27
  "@types/react": "^18.2.66",
33
28
  "@types/react-dom": "^18.2.22",
34
29
  "@types/express": "^4.17.21",
35
30
  "@types/cors": "^2.8.17",
36
31
  "@vitejs/plugin-react-swc": "^3.7.2",
37
- "typescript": "^5.2.2"
32
+ "typescript": "^5.2.2",
33
+ "@akinon/typescript-config": "1.0.1"
38
34
  },
39
35
  "engines": {
40
36
  "node": ">=18"
@@ -1,64 +1,78 @@
1
- import { Layout } from '@akinon/ui-layout';
1
+ import './layout.scss';
2
+
3
+ import { Layout, Space, Text } from '@akinon/ui-react';
2
4
  import React, { useState } from 'react';
3
5
 
4
- import { getSidebarItems, getTheme } from '../themes';
5
- import type { ShellConfig } from '../types';
6
+ import type { ShellConfig, ViewType } from '../types';
7
+ import { AppRouter } from './AppRouter';
6
8
  import { Header } from './Header';
7
- import { PluginLoader } from './PluginLoader';
8
- import { Sidebar } from './Sidebar';
9
+ import { Navigation } from './Navigation';
9
10
 
10
11
  interface AppLayoutProps {
11
12
  config: ShellConfig;
12
13
  }
13
14
 
14
15
  export const AppLayout: React.FC<AppLayoutProps> = ({ config }) => {
15
- const [collapsed, setCollapsed] = useState(false);
16
- const theme = getTheme(config.shell.theme);
17
- const sidebarItems =
18
- config.sidebar?.items || getSidebarItems(config.shell.theme);
16
+ const [leftSiderCollapsed, setLeftSiderCollapsed] = useState(false);
17
+ const [rightSiderCollapsed, setRightSiderCollapsed] = useState(true);
18
+ const [currentView, setCurrentView] = useState<ViewType>('welcome');
19
19
 
20
- const sidebarWidth = collapsed
21
- ? theme.sidebar.collapsedWidth
22
- : theme.sidebar.width;
20
+ const toggleLeftSider = () => setLeftSiderCollapsed(!leftSiderCollapsed);
21
+ const toggleRightSider = () => setRightSiderCollapsed(!rightSiderCollapsed);
23
22
 
24
23
  return (
25
- <Layout
26
- style={{ minHeight: '100vh', backgroundColor: theme.colors.background }}
27
- >
28
- <Sidebar items={sidebarItems} theme={theme} collapsed={collapsed} />
29
-
30
- <Layout
31
- style={{ marginLeft: sidebarWidth, transition: 'margin-left 0.2s' }}
24
+ <Layout>
25
+ <Layout.Sider
26
+ collapsed={leftSiderCollapsed}
27
+ brandImageUrl="/akinon-logo.svg"
28
+ iconUrl="/akinon-favicon.svg"
32
29
  >
33
- <Header
34
- theme={theme}
35
- title={config.shell.title}
36
- collapsed={collapsed}
37
- onToggleCollapse={() => setCollapsed(!collapsed)}
38
- sidebarWidth={sidebarWidth}
30
+ <Navigation
31
+ config={config}
32
+ currentView={currentView}
33
+ onNavigate={setCurrentView}
39
34
  />
35
+ </Layout.Sider>
40
36
 
41
- <Layout.Content
42
- style={{
43
- marginTop: theme.header.height,
44
- padding: '24px',
45
- minHeight: `calc(100vh - ${theme.header.height}px)`,
46
- backgroundColor: theme.colors.background
47
- }}
48
- >
49
- <div
50
- style={{
51
- backgroundColor: 'white',
52
- borderRadius: '8px',
53
- height: 'calc(100vh - 112px)',
54
- overflow: 'hidden',
55
- boxShadow: '0 2px 8px rgba(0, 0, 0, 0.06)'
56
- }}
57
- >
58
- <PluginLoader config={config} />
59
- </div>
37
+ <Layout>
38
+ <Layout.Header>
39
+ <Header
40
+ onToggleLeftSider={toggleLeftSider}
41
+ onToggleRightSider={toggleRightSider}
42
+ />
43
+ </Layout.Header>
44
+
45
+ <Layout.Content>
46
+ <AppRouter
47
+ config={config}
48
+ currentView={currentView}
49
+ onNavigate={setCurrentView}
50
+ />
60
51
  </Layout.Content>
52
+
53
+ <Layout.Footer>
54
+ {config.shell.title} &copy; {new Date().getFullYear()} Created by
55
+ Akinon
56
+ </Layout.Footer>
61
57
  </Layout>
58
+
59
+ {/* Development logs/notifications area */}
60
+ <Layout.Sider
61
+ collapsed={rightSiderCollapsed}
62
+ collapsedWidth={0}
63
+ position="right"
64
+ >
65
+ <Space className="log-messages">
66
+ <div className="log-messages-item">
67
+ <Text type="success">Development server running</Text>
68
+ <Text>{new Date().toLocaleTimeString()}</Text>
69
+ </div>
70
+ <div className="log-messages-item">
71
+ <Text>Plugin URL: {config.plugin.url}</Text>
72
+ <Text>Type: {config.plugin.type}</Text>
73
+ </div>
74
+ </Space>
75
+ </Layout.Sider>
62
76
  </Layout>
63
77
  );
64
78
  };
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+
3
+ import type { ShellConfig, ViewType } from '../types';
4
+ import { PluginLoader } from './PluginLoader';
5
+ import { WelcomePage } from './WelcomePage';
6
+ import { PluginTestPage } from './PluginTestPage';
7
+
8
+ interface AppRouterProps {
9
+ config: ShellConfig;
10
+ currentView: ViewType;
11
+ onNavigate: (view: ViewType) => void;
12
+ }
13
+
14
+ export const AppRouter: React.FC<AppRouterProps> = ({ config, currentView, onNavigate }) => {
15
+ const renderView = () => {
16
+ switch (currentView) {
17
+ case 'welcome':
18
+ return (
19
+ <div className="px-8 py-8">
20
+ <WelcomePage config={config} onNavigate={onNavigate} />
21
+ </div>
22
+ );
23
+ case 'app':
24
+ return <PluginLoader config={config} />;
25
+ case 'plugin-test':
26
+ return (
27
+ <div className="px-8 py-8">
28
+ <PluginTestPage config={config} onNavigate={onNavigate} />
29
+ </div>
30
+ );
31
+ default:
32
+ return (
33
+ <div className="px-8 py-8">
34
+ <WelcomePage config={config} onNavigate={onNavigate} />
35
+ </div>
36
+ );
37
+ }
38
+ };
39
+
40
+ return renderView();
41
+ };
@@ -1,66 +1,42 @@
1
- import { Button } from '@akinon/ui-button';
2
- import { Layout } from '@akinon/ui-layout';
1
+ import { Button } from '@akinon/ui-react';
3
2
  import React from 'react';
4
3
 
5
- import type { ThemeConfig } from '../types';
4
+ import { HeaderSearch } from './HeaderSearch';
5
+ import { HeaderUser } from './HeaderUser';
6
6
 
7
7
  interface HeaderProps {
8
- theme: ThemeConfig;
9
- title: string;
10
- collapsed: boolean;
11
- onToggleCollapse: () => void;
12
- sidebarWidth: number;
8
+ onToggleLeftSider: () => void;
9
+ onToggleRightSider: () => void;
13
10
  }
14
11
 
15
- export const Header: React.FC<HeaderProps> = ({
16
- theme,
17
- title,
18
- collapsed,
19
- onToggleCollapse,
20
- sidebarWidth
12
+ export const Header: React.FC<HeaderProps> = ({
13
+ onToggleLeftSider,
14
+ onToggleRightSider
21
15
  }) => {
22
16
  return (
23
- <Layout.Header
24
- style={{
25
- backgroundColor: theme.colors.header,
26
- padding: '0 24px',
27
- height: theme.header.height,
28
- lineHeight: `${theme.header.height}px`,
29
- position: 'fixed',
30
- top: 0,
31
- right: 0,
32
- left: sidebarWidth,
33
- zIndex: 100,
34
- boxShadow: '0 2px 8px rgba(0, 0, 0, 0.06)',
35
- display: 'flex',
36
- alignItems: 'center',
37
- justifyContent: 'space-between',
38
- transition: 'left 0.2s'
39
- }}
40
- >
41
- <div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
17
+ <>
18
+ <div className="flex items-center">
42
19
  <Button
43
20
  type="text"
44
- onClick={onToggleCollapse}
45
- style={{ fontSize: '16px' }}
46
- >
47
- {collapsed ? '☰' : '⬅'}
48
- </Button>
49
- <h1 style={{ margin: 0, fontSize: '18px', fontWeight: '500' }}>
50
- {title}
51
- </h1>
21
+ icon="outdent"
22
+ onClick={onToggleLeftSider}
23
+ className="mr-2"
24
+ />
25
+ </div>
26
+
27
+ <div>
28
+ <HeaderSearch />
52
29
  </div>
53
- <div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
54
- <span style={{ color: '#666' }}>Development Mode</span>
55
- <div
56
- style={{
57
- width: 8,
58
- height: 8,
59
- borderRadius: '50%',
60
- backgroundColor: '#52c41a'
61
- }}
30
+
31
+ <div className="flex items-center">
32
+ <HeaderUser />
33
+ <Button
34
+ type="text"
35
+ icon="bell"
36
+ onClick={onToggleRightSider}
37
+ className="mr-2"
62
38
  />
63
39
  </div>
64
- </Layout.Header>
40
+ </>
65
41
  );
66
- };
42
+ };
@@ -0,0 +1,12 @@
1
+ import { InputSearch } from '@akinon/ui-react';
2
+ import React from 'react';
3
+
4
+ export const HeaderSearch: React.FC = () => {
5
+ return (
6
+ <InputSearch
7
+ placeholder="Search..."
8
+ style={{ width: 300 }}
9
+ onSearch={() => {}}
10
+ />
11
+ );
12
+ };
@@ -0,0 +1,23 @@
1
+ import { Avatar, Dropdown } from '@akinon/ui-react';
2
+ import React from 'react';
3
+
4
+ export const HeaderUser: React.FC = () => {
5
+ return (
6
+ <Dropdown
7
+ menu={{
8
+ items: [
9
+ { key: 'profile', label: 'Profile' },
10
+ { key: 'settings', label: 'Settings' },
11
+ { key: 'divider', type: 'divider' },
12
+ { key: 'logout', label: 'Logout' }
13
+ ]
14
+ }}
15
+ placement="bottomRight"
16
+ >
17
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
18
+ <Avatar size="small">Dev</Avatar>
19
+ <span style={{ fontSize: '14px' }}>Developer</span>
20
+ </div>
21
+ </Dropdown>
22
+ );
23
+ };
@@ -0,0 +1,82 @@
1
+ import { Menu, IMenuProps } from '@akinon/ui-react';
2
+ import React from 'react';
3
+
4
+ import type { ShellConfig, ViewType } from '../types';
5
+
6
+ interface NavigationProps {
7
+ config: ShellConfig;
8
+ currentView: ViewType;
9
+ onNavigate: (view: ViewType) => void;
10
+ }
11
+
12
+ export const Navigation: React.FC<NavigationProps> = ({
13
+ config,
14
+ currentView,
15
+ onNavigate
16
+ }) => {
17
+ const getMenuItems = () => {
18
+ const items = [
19
+ {
20
+ key: 'welcome',
21
+ label: 'Welcome',
22
+ icon: 'home'
23
+ },
24
+ {
25
+ key: 'development',
26
+ label: 'Development',
27
+ icon: 'build',
28
+ children: [
29
+ {
30
+ key: 'app',
31
+ label: config.plugin.name,
32
+ icon: config.plugin.type === 'fullpage' ? 'desktop' : 'appstore'
33
+ }
34
+ ]
35
+ }
36
+ ];
37
+
38
+ // Add plugin test page for plugin apps
39
+ if (config.plugin.type === 'plugin') {
40
+ items[1].children.push({
41
+ key: 'plugin-test',
42
+ label: 'Plugin Test',
43
+ icon: 'experiment'
44
+ });
45
+ }
46
+
47
+ // Add static menu items to match playground
48
+ items.push(
49
+ {
50
+ key: 'settings',
51
+ label: 'Settings',
52
+ icon: 'setting'
53
+ },
54
+ {
55
+ key: 'help',
56
+ label: 'Help & Support',
57
+ icon: 'question-circle'
58
+ }
59
+ );
60
+
61
+ return items;
62
+ };
63
+
64
+ const onClick: IMenuProps['onClick'] = (e: any) => {
65
+ if (e.key === 'welcome' || e.key === 'app' || e.key === 'plugin-test') {
66
+ onNavigate(e.key as ViewType);
67
+ }
68
+ };
69
+
70
+ return (
71
+ <div>
72
+ <Menu
73
+ mode="inline"
74
+ selectedKeys={[currentView]}
75
+ defaultOpenKeys={['development']}
76
+ items={getMenuItems()}
77
+ onClick={onClick}
78
+ theme="dark"
79
+ />
80
+ </div>
81
+ );
82
+ };
@@ -0,0 +1,223 @@
1
+ import { Button, Card } from '@akinon/ui-react';
2
+ import React, { useState } from 'react';
3
+
4
+ import type { ShellConfig } from '../types';
5
+ import { PluginLoader } from './PluginLoader';
6
+
7
+ interface PluginTestPageProps {
8
+ config: ShellConfig;
9
+ onNavigate: (view: 'welcome' | 'app' | 'plugin-test') => void;
10
+ }
11
+
12
+ type PlaceholderType = 'sidebar' | 'header' | 'content' | 'modal' | 'drawer';
13
+
14
+ const placeholderConfigs = {
15
+ sidebar: {
16
+ name: 'Sidebar Plugin',
17
+ description: 'Plugin rendered in sidebar area',
18
+ dimensions: '300px × 100%',
19
+ containerStyle: {
20
+ width: '300px',
21
+ height: '400px',
22
+ border: '2px dashed #1890ff',
23
+ borderRadius: '4px',
24
+ backgroundColor: '#f0f8ff'
25
+ }
26
+ },
27
+ header: {
28
+ name: 'Header Plugin',
29
+ description: 'Plugin rendered in header area',
30
+ dimensions: '100% × 60px',
31
+ containerStyle: {
32
+ width: '100%',
33
+ height: '60px',
34
+ border: '2px dashed #52c41a',
35
+ borderRadius: '4px',
36
+ backgroundColor: '#f6ffed'
37
+ }
38
+ },
39
+ content: {
40
+ name: 'Content Plugin',
41
+ description: 'Plugin rendered in main content area',
42
+ dimensions: '100% × 400px',
43
+ containerStyle: {
44
+ width: '100%',
45
+ height: '400px',
46
+ border: '2px dashed #722ed1',
47
+ borderRadius: '4px',
48
+ backgroundColor: '#f9f0ff'
49
+ }
50
+ },
51
+ modal: {
52
+ name: 'Modal Plugin',
53
+ description: 'Plugin rendered in modal overlay',
54
+ dimensions: '600px × 400px',
55
+ containerStyle: {
56
+ width: '600px',
57
+ height: '400px',
58
+ border: '2px dashed #fa541c',
59
+ borderRadius: '4px',
60
+ backgroundColor: '#fff7e6'
61
+ }
62
+ },
63
+ drawer: {
64
+ name: 'Drawer Plugin',
65
+ description: 'Plugin rendered in drawer panel',
66
+ dimensions: '400px × 100%',
67
+ containerStyle: {
68
+ width: '400px',
69
+ height: '300px',
70
+ border: '2px dashed #eb2f96',
71
+ borderRadius: '4px',
72
+ backgroundColor: '#fff0f6'
73
+ }
74
+ }
75
+ };
76
+
77
+ export const PluginTestPage: React.FC<PluginTestPageProps> = ({ config, onNavigate }) => {
78
+ const [selectedPlaceholder, setSelectedPlaceholder] = useState<PlaceholderType>('content');
79
+ const [isPluginVisible, setIsPluginVisible] = useState(false);
80
+
81
+ const currentConfig = placeholderConfigs[selectedPlaceholder];
82
+
83
+ return (
84
+ <div style={{ padding: '24px' }}>
85
+ <div style={{ marginBottom: '24px', display: 'flex', alignItems: 'center', gap: '16px' }}>
86
+ <Button onClick={() => onNavigate('welcome')}>← Back to Welcome</Button>
87
+ <h2 style={{ margin: 0 }}>Plugin Integration Test</h2>
88
+ </div>
89
+
90
+ <div style={{ display: 'grid', gridTemplateColumns: '300px 1fr', gap: '24px' }}>
91
+ <Card title="Test Configuration" size="small">
92
+ <div style={{ marginBottom: '16px' }}>
93
+ <label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
94
+ Placeholder Type:
95
+ </label>
96
+ <select
97
+ value={selectedPlaceholder}
98
+ onChange={(e) => setSelectedPlaceholder(e.target.value as PlaceholderType)}
99
+ style={{
100
+ width: '100%',
101
+ padding: '8px 12px',
102
+ border: '1px solid #d9d9d9',
103
+ borderRadius: '6px',
104
+ fontSize: '14px',
105
+ backgroundColor: 'white'
106
+ }}
107
+ >
108
+ {Object.entries(placeholderConfigs).map(([key, config]) => (
109
+ <option key={key} value={key}>
110
+ {config.name}
111
+ </option>
112
+ ))}
113
+ </select>
114
+ </div>
115
+
116
+ <div style={{ marginBottom: '16px' }}>
117
+ <label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
118
+ Plugin Status:
119
+ </label>
120
+ <span style={{
121
+ padding: '4px 8px',
122
+ borderRadius: '4px',
123
+ fontSize: '12px',
124
+ backgroundColor: isPluginVisible ? '#f6ffed' : '#f5f5f5',
125
+ color: isPluginVisible ? '#52c41a' : '#666',
126
+ border: `1px solid ${isPluginVisible ? '#b7eb8f' : '#d9d9d9'}`
127
+ }}>
128
+ {isPluginVisible ? 'Visible' : 'Hidden'}
129
+ </span>
130
+ </div>
131
+
132
+ <div style={{ marginBottom: '16px' }}>
133
+ <Button
134
+ type="primary"
135
+ block
136
+ onClick={() => setIsPluginVisible(!isPluginVisible)}
137
+ >
138
+ {isPluginVisible ? 'Hide Plugin' : 'Show Plugin'}
139
+ </Button>
140
+ </div>
141
+
142
+ <div style={{ fontSize: '12px', color: '#666' }}>
143
+ <p><strong>Dimensions:</strong> {currentConfig.dimensions}</p>
144
+ <p><strong>Description:</strong> {currentConfig.description}</p>
145
+ </div>
146
+ </Card>
147
+
148
+ <Card title={`${currentConfig.name} Preview`} size="small">
149
+ <div style={{
150
+ padding: '24px',
151
+ backgroundColor: '#fafafa',
152
+ borderRadius: '4px',
153
+ minHeight: '500px',
154
+ display: 'flex',
155
+ alignItems: 'center',
156
+ justifyContent: 'center'
157
+ }}>
158
+ <div style={currentConfig.containerStyle}>
159
+ {isPluginVisible ? (
160
+ <div style={{ width: '100%', height: '100%', position: 'relative' }}>
161
+ <div style={{
162
+ position: 'absolute',
163
+ top: '8px',
164
+ left: '8px',
165
+ right: '8px',
166
+ backgroundColor: 'rgba(0,0,0,0.7)',
167
+ color: 'white',
168
+ padding: '4px 8px',
169
+ borderRadius: '2px',
170
+ fontSize: '10px',
171
+ zIndex: 10
172
+ }}>
173
+ Plugin: {config.plugin.name} ({selectedPlaceholder})
174
+ </div>
175
+ <PluginLoader config={config} />
176
+ </div>
177
+ ) : (
178
+ <div style={{
179
+ display: 'flex',
180
+ flexDirection: 'column',
181
+ alignItems: 'center',
182
+ justifyContent: 'center',
183
+ height: '100%',
184
+ color: '#999',
185
+ textAlign: 'center'
186
+ }}>
187
+ <div style={{ fontSize: '48px', marginBottom: '8px' }}>📦</div>
188
+ <p style={{ margin: 0, fontSize: '14px' }}>
189
+ {currentConfig.name} Placeholder
190
+ </p>
191
+ <p style={{ margin: '4px 0 0 0', fontSize: '12px' }}>
192
+ Click "Show Plugin" to load
193
+ </p>
194
+ </div>
195
+ )}
196
+ </div>
197
+ </div>
198
+ </Card>
199
+ </div>
200
+
201
+ <Card
202
+ title="Integration Notes"
203
+ style={{ marginTop: '24px' }}
204
+ size="small"
205
+ >
206
+ <div style={{ fontSize: '14px', lineHeight: '1.5' }}>
207
+ <p>
208
+ This test environment simulates how your plugin will appear in different
209
+ placeholder configurations within host applications. Each placeholder type
210
+ represents a different integration scenario:
211
+ </p>
212
+ <ul>
213
+ <li><strong>Sidebar:</strong> Narrow vertical space, typically for navigation or quick actions</li>
214
+ <li><strong>Header:</strong> Horizontal space at top, usually for search or user controls</li>
215
+ <li><strong>Content:</strong> Main content area with full width and flexible height</li>
216
+ <li><strong>Modal:</strong> Overlay dialog for focused interactions</li>
217
+ <li><strong>Drawer:</strong> Slide-out panel for secondary content or forms</li>
218
+ </ul>
219
+ </div>
220
+ </Card>
221
+ </div>
222
+ );
223
+ };