astryx-dracula 0.1.3 → 0.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/BRAND.md +1 -1
- package/README.md +2 -0
- package/USAGE.md +13 -0
- package/astryx-dracula.js +4 -4
- package/astryx.integration.mjs +3 -0
- package/package.json +4 -2
- package/templates/ai-chat-landing.template.mjs +6 -0
- package/templates/ai-chat-landing.tsx +476 -0
- package/templates/ai-chat.template.mjs +6 -0
- package/templates/ai-chat.tsx +762 -0
- package/templates/blank.template.mjs +6 -0
- package/templates/blank.tsx +16 -0
- package/templates/centered-hero.template.mjs +6 -0
- package/templates/centered-hero.tsx +188 -0
- package/templates/classic-gallery.template.mjs +6 -0
- package/templates/classic-gallery.tsx +181 -0
- package/templates/contact-form.template.mjs +6 -0
- package/templates/contact-form.tsx +292 -0
- package/templates/dashboard-portfolio.template.mjs +6 -0
- package/templates/dashboard-portfolio.tsx +800 -0
- package/templates/dashboard.template.mjs +6 -0
- package/templates/dashboard.tsx +774 -0
- package/templates/detail-page.template.mjs +6 -0
- package/templates/detail-page.tsx +695 -0
- package/templates/documentation-design.template.mjs +6 -0
- package/templates/documentation-design.tsx +751 -0
- package/templates/documentation-technical.template.mjs +6 -0
- package/templates/documentation-technical.tsx +292 -0
- package/templates/documentation.template.mjs +6 -0
- package/templates/documentation.tsx +347 -0
- package/templates/editor.template.mjs +6 -0
- package/templates/editor.tsx +959 -0
- package/templates/file-explorer.template.mjs +6 -0
- package/templates/file-explorer.tsx +577 -0
- package/templates/form-two-column.template.mjs +6 -0
- package/templates/form-two-column.tsx +314 -0
- package/templates/gallery-hero.template.mjs +6 -0
- package/templates/gallery-hero.tsx +287 -0
- package/templates/ide.template.mjs +6 -0
- package/templates/ide.tsx +533 -0
- package/templates/incident-console.template.mjs +6 -0
- package/templates/incident-console.tsx +584 -0
- package/templates/kanban-board.template.mjs +6 -0
- package/templates/kanban-board.tsx +746 -0
- package/templates/library.template.mjs +6 -0
- package/templates/library.tsx +489 -0
- package/templates/login-card.template.mjs +6 -0
- package/templates/login-card.tsx +200 -0
- package/templates/login-split.template.mjs +6 -0
- package/templates/login-split.tsx +318 -0
- package/templates/login-sso.template.mjs +6 -0
- package/templates/login-sso.tsx +277 -0
- package/templates/login.template.mjs +6 -0
- package/templates/login.tsx +98 -0
- package/templates/messaging-shell.template.mjs +6 -0
- package/templates/messaging-shell.tsx +675 -0
- package/templates/mixed-gallery.template.mjs +6 -0
- package/templates/mixed-gallery.tsx +190 -0
- package/templates/payment-form.template.mjs +6 -0
- package/templates/payment-form.tsx +990 -0
- package/templates/product-detail.template.mjs +6 -0
- package/templates/product-detail.tsx +366 -0
- package/templates/product-gallery.template.mjs +6 -0
- package/templates/product-gallery.tsx +173 -0
- package/templates/settings-dialog.template.mjs +6 -0
- package/templates/settings-dialog.tsx +920 -0
- package/templates/settings-sidebar.template.mjs +6 -0
- package/templates/settings-sidebar.tsx +952 -0
- package/templates/settings.template.mjs +6 -0
- package/templates/settings.tsx +235 -0
- package/templates/shell-nav.template.mjs +6 -0
- package/templates/shell-nav.tsx +306 -0
- package/templates/shell-side-nav.template.mjs +6 -0
- package/templates/shell-side-nav.tsx +238 -0
- package/templates/shell-top-nav.template.mjs +6 -0
- package/templates/shell-top-nav.tsx +243 -0
- package/templates/side-gallery.template.mjs +6 -0
- package/templates/side-gallery.tsx +179 -0
- package/templates/table-grouped.template.mjs +6 -0
- package/templates/table-grouped.tsx +1174 -0
- package/templates/table-page-chart.template.mjs +6 -0
- package/templates/table-page-chart.tsx +693 -0
- package/templates/table-page-heatmap-status.template.mjs +6 -0
- package/templates/table-page-heatmap-status.tsx +547 -0
- package/templates/table-page-shoe-store-heatmap.template.mjs +6 -0
- package/templates/table-page-shoe-store-heatmap.tsx +1038 -0
- package/templates/table-page.template.mjs +6 -0
- package/templates/table-page.tsx +445 -0
- package/templates/table.template.mjs +6 -0
- package/templates/table.tsx +84 -0
- package/templates/theme-showcase.template.mjs +6 -0
- package/templates/theme-showcase.tsx +1282 -0
- package/theme.css +4 -4
- package/tokens.css +4 -4
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {useState} from 'react';
|
|
4
|
+
import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
5
|
+
import {
|
|
6
|
+
VStack,
|
|
7
|
+
HStack,
|
|
8
|
+
StackItem,
|
|
9
|
+
Layout,
|
|
10
|
+
LayoutContent,
|
|
11
|
+
LayoutHeader,
|
|
12
|
+
LayoutPanel,
|
|
13
|
+
} from '@astryxdesign/core/Layout';
|
|
14
|
+
import {Grid} from '@astryxdesign/core/Grid';
|
|
15
|
+
import {List, ListItem} from '@astryxdesign/core/List';
|
|
16
|
+
import {TabList, Tab, TabMenu} from '@astryxdesign/core/TabList';
|
|
17
|
+
import {Text, Heading} from '@astryxdesign/core/Text';
|
|
18
|
+
import {TextInput} from '@astryxdesign/core/TextInput';
|
|
19
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
20
|
+
import {Divider} from '@astryxdesign/core/Divider';
|
|
21
|
+
import {CheckboxInput} from '@astryxdesign/core/CheckboxInput';
|
|
22
|
+
import {Typeahead} from '@astryxdesign/core/Typeahead';
|
|
23
|
+
import {Search} from 'lucide-react';
|
|
24
|
+
import type {SearchableItem, SearchSource} from '@astryxdesign/core/Typeahead';
|
|
25
|
+
|
|
26
|
+
const NAV_ITEMS = [
|
|
27
|
+
'Profile',
|
|
28
|
+
'Account',
|
|
29
|
+
'Members',
|
|
30
|
+
'Billing',
|
|
31
|
+
'Invoices',
|
|
32
|
+
'API',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const SETTINGS_ITEMS: SearchableItem[] = [
|
|
36
|
+
{id: '1', label: 'Username'},
|
|
37
|
+
{id: '2', label: 'First name'},
|
|
38
|
+
{id: '3', label: 'Last name'},
|
|
39
|
+
{id: '4', label: 'Email address'},
|
|
40
|
+
{id: '5', label: 'Change password'},
|
|
41
|
+
{id: '6', label: 'Data Export Access'},
|
|
42
|
+
{id: '7', label: 'Allow Admin to Add Members'},
|
|
43
|
+
{id: '8', label: 'Two-Factor Authentication'},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const settingsSearchSource: SearchSource<SearchableItem> = {
|
|
47
|
+
search: (query: string) =>
|
|
48
|
+
SETTINGS_ITEMS.filter(item =>
|
|
49
|
+
item.label.toLowerCase().includes(query.toLowerCase()),
|
|
50
|
+
),
|
|
51
|
+
bootstrap: () => SETTINGS_ITEMS,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default function SettingsTemplate() {
|
|
55
|
+
const isNarrow = useMediaQuery('(max-width: 768px)');
|
|
56
|
+
const [activeNav, setActiveNav] = useState('Profile');
|
|
57
|
+
const [username, setUsername] = useState('vlad_tepes');
|
|
58
|
+
const [firstName, setFirstName] = useState('Vlad');
|
|
59
|
+
const [lastName, setLastName] = useState('Tepes');
|
|
60
|
+
const [email, setEmail] = useState('vlad_tepes@castle-dracula.ro');
|
|
61
|
+
const [currentPw, setCurrentPw] = useState('password123');
|
|
62
|
+
const [newPw, setNewPw] = useState('password123');
|
|
63
|
+
const [confirmPw, setConfirmPw] = useState('password123');
|
|
64
|
+
const [dataExport, setDataExport] = useState(false);
|
|
65
|
+
const [adminMembers, setAdminMembers] = useState(false);
|
|
66
|
+
const [twoFactor, setTwoFactor] = useState(false);
|
|
67
|
+
const [searchValue, setSearchValue] = useState<SearchableItem | null>(null);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Layout
|
|
71
|
+
height="fill"
|
|
72
|
+
contentWidth={1440}
|
|
73
|
+
header={
|
|
74
|
+
<LayoutHeader hasDivider>
|
|
75
|
+
<HStack vAlign="center">
|
|
76
|
+
<StackItem size="fill">
|
|
77
|
+
<Heading level={1}>Settings</Heading>
|
|
78
|
+
</StackItem>
|
|
79
|
+
<Typeahead
|
|
80
|
+
label="Search"
|
|
81
|
+
isLabelHidden
|
|
82
|
+
placeholder="Search coven settings..."
|
|
83
|
+
searchSource={settingsSearchSource}
|
|
84
|
+
value={searchValue}
|
|
85
|
+
onChange={setSearchValue}
|
|
86
|
+
hasEntriesOnFocus
|
|
87
|
+
startIcon={Search}
|
|
88
|
+
/>
|
|
89
|
+
</HStack>
|
|
90
|
+
</LayoutHeader>
|
|
91
|
+
}
|
|
92
|
+
start={
|
|
93
|
+
isNarrow ? undefined : (
|
|
94
|
+
<LayoutPanel hasDivider={false} width={260} padding={2}>
|
|
95
|
+
<List density="balanced">
|
|
96
|
+
{NAV_ITEMS.map(item => (
|
|
97
|
+
<ListItem
|
|
98
|
+
key={item}
|
|
99
|
+
label={item}
|
|
100
|
+
isSelected={activeNav === item}
|
|
101
|
+
onClick={() => setActiveNav(item)}
|
|
102
|
+
/>
|
|
103
|
+
))}
|
|
104
|
+
</List>
|
|
105
|
+
</LayoutPanel>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
content={
|
|
109
|
+
<LayoutContent padding={4}>
|
|
110
|
+
<VStack gap={4}>
|
|
111
|
+
{/* Mobile: the sidebar nav collapses to a horizontal, centered
|
|
112
|
+
tab bar above the content. */}
|
|
113
|
+
{isNarrow && (
|
|
114
|
+
<VStack hAlign="center">
|
|
115
|
+
<TabList value={activeNav} onChange={setActiveNav}>
|
|
116
|
+
{NAV_ITEMS.slice(0, 3).map(item => (
|
|
117
|
+
<Tab key={item} value={item} label={item} />
|
|
118
|
+
))}
|
|
119
|
+
<TabMenu
|
|
120
|
+
label="More"
|
|
121
|
+
options={NAV_ITEMS.slice(3).map(item => ({
|
|
122
|
+
value: item,
|
|
123
|
+
label: item,
|
|
124
|
+
}))}
|
|
125
|
+
/>
|
|
126
|
+
</TabList>
|
|
127
|
+
</VStack>
|
|
128
|
+
)}
|
|
129
|
+
<Grid columns={{minWidth: 320}} gap={10}>
|
|
130
|
+
<VStack gap={1}>
|
|
131
|
+
<Heading level={3}>Basic information</Heading>
|
|
132
|
+
<Text type="body" color="secondary">
|
|
133
|
+
View and update your crypt details and coven account information.
|
|
134
|
+
</Text>
|
|
135
|
+
</VStack>
|
|
136
|
+
<VStack gap={4}>
|
|
137
|
+
<TextInput
|
|
138
|
+
label="Username"
|
|
139
|
+
value={username}
|
|
140
|
+
onChange={setUsername}
|
|
141
|
+
/>
|
|
142
|
+
<TextInput
|
|
143
|
+
label="First name"
|
|
144
|
+
value={firstName}
|
|
145
|
+
onChange={setFirstName}
|
|
146
|
+
/>
|
|
147
|
+
<TextInput
|
|
148
|
+
label="Last name"
|
|
149
|
+
value={lastName}
|
|
150
|
+
onChange={setLastName}
|
|
151
|
+
/>
|
|
152
|
+
<TextInput
|
|
153
|
+
label="Email address"
|
|
154
|
+
value={email}
|
|
155
|
+
onChange={setEmail}
|
|
156
|
+
/>
|
|
157
|
+
<HStack>
|
|
158
|
+
<Button label="Save" variant="primary" />
|
|
159
|
+
</HStack>
|
|
160
|
+
</VStack>
|
|
161
|
+
</Grid>
|
|
162
|
+
|
|
163
|
+
<Divider />
|
|
164
|
+
|
|
165
|
+
<Grid columns={{minWidth: 320}} gap={10}>
|
|
166
|
+
<VStack gap={1}>
|
|
167
|
+
<Heading level={3}>Change password</Heading>
|
|
168
|
+
<Text type="body" color="secondary">
|
|
169
|
+
Update your password to keep your coffin sealed.
|
|
170
|
+
</Text>
|
|
171
|
+
</VStack>
|
|
172
|
+
<VStack gap={4}>
|
|
173
|
+
<TextInput
|
|
174
|
+
label="Verify current password"
|
|
175
|
+
type="password"
|
|
176
|
+
value={currentPw}
|
|
177
|
+
onChange={setCurrentPw}
|
|
178
|
+
/>
|
|
179
|
+
<TextInput
|
|
180
|
+
label="New password"
|
|
181
|
+
type="password"
|
|
182
|
+
value={newPw}
|
|
183
|
+
onChange={setNewPw}
|
|
184
|
+
/>
|
|
185
|
+
<TextInput
|
|
186
|
+
label="Confirm password"
|
|
187
|
+
type="password"
|
|
188
|
+
value={confirmPw}
|
|
189
|
+
onChange={setConfirmPw}
|
|
190
|
+
/>
|
|
191
|
+
<HStack>
|
|
192
|
+
<Button label="Save" variant="primary" />
|
|
193
|
+
</HStack>
|
|
194
|
+
</VStack>
|
|
195
|
+
</Grid>
|
|
196
|
+
|
|
197
|
+
<Divider />
|
|
198
|
+
|
|
199
|
+
<Grid columns={{minWidth: 320}} gap={10}>
|
|
200
|
+
<VStack gap={1}>
|
|
201
|
+
<Heading level={3}>Advanced settings</Heading>
|
|
202
|
+
<Text type="body" color="secondary">
|
|
203
|
+
Configure detailed coven preferences and warding options.
|
|
204
|
+
</Text>
|
|
205
|
+
</VStack>
|
|
206
|
+
<VStack gap={5}>
|
|
207
|
+
<CheckboxInput
|
|
208
|
+
label="Data Export Access"
|
|
209
|
+
description="Allow export of personal data and backups."
|
|
210
|
+
value={dataExport}
|
|
211
|
+
onChange={setDataExport}
|
|
212
|
+
/>
|
|
213
|
+
<CheckboxInput
|
|
214
|
+
label="Allow Admin to Add Members"
|
|
215
|
+
description="Admins can invite and manage members."
|
|
216
|
+
value={adminMembers}
|
|
217
|
+
onChange={setAdminMembers}
|
|
218
|
+
/>
|
|
219
|
+
<CheckboxInput
|
|
220
|
+
label="Enable Two-Factor Authentication"
|
|
221
|
+
description="Require 2FA for added account security."
|
|
222
|
+
value={twoFactor}
|
|
223
|
+
onChange={setTwoFactor}
|
|
224
|
+
/>
|
|
225
|
+
<HStack>
|
|
226
|
+
<Button label="Save" variant="primary" />
|
|
227
|
+
</HStack>
|
|
228
|
+
</VStack>
|
|
229
|
+
</Grid>
|
|
230
|
+
</VStack>
|
|
231
|
+
</LayoutContent>
|
|
232
|
+
}
|
|
233
|
+
/>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {Fragment, useState, useMemo, useEffect} from 'react';
|
|
4
|
+
import {AppShell} from '@astryxdesign/core/AppShell';
|
|
5
|
+
import {Layout, LayoutHeader, LayoutContent} from '@astryxdesign/core/Layout';
|
|
6
|
+
import {TopNav} from '@astryxdesign/core/TopNav';
|
|
7
|
+
import {DropdownMenu, DropdownMenuItem} from '@astryxdesign/core/DropdownMenu';
|
|
8
|
+
import {CommandPalette} from '@astryxdesign/core/CommandPalette';
|
|
9
|
+
import {createStaticSource} from '@astryxdesign/core/Typeahead';
|
|
10
|
+
import {Divider} from '@astryxdesign/core/Divider';
|
|
11
|
+
import {Kbd} from '@astryxdesign/core/Kbd';
|
|
12
|
+
import {SideNav} from '@astryxdesign/core/SideNav';
|
|
13
|
+
import {TreeList} from '@astryxdesign/core/TreeList';
|
|
14
|
+
import type {TreeListItemData} from '@astryxdesign/core/TreeList';
|
|
15
|
+
import {Icon} from '@astryxdesign/core/Icon';
|
|
16
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
17
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
18
|
+
import {Card} from '@astryxdesign/core/Card';
|
|
19
|
+
import {VStack, HStack} from '@astryxdesign/core/Stack';
|
|
20
|
+
import {Search, Folder, FileText} from 'lucide-react';
|
|
21
|
+
|
|
22
|
+
const noop = () => {};
|
|
23
|
+
|
|
24
|
+
const folder = (
|
|
25
|
+
id: string,
|
|
26
|
+
children: TreeListItemData[],
|
|
27
|
+
isExpanded = true,
|
|
28
|
+
): TreeListItemData => ({
|
|
29
|
+
id,
|
|
30
|
+
label: <Text maxLines={1}>{id}</Text>,
|
|
31
|
+
startContent: <Icon icon={Folder} size="xsm" />,
|
|
32
|
+
isExpanded,
|
|
33
|
+
children,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const file = (id: string, isSelected = false): TreeListItemData => ({
|
|
37
|
+
id,
|
|
38
|
+
label: <Text maxLines={1}>{id}</Text>,
|
|
39
|
+
startContent: <Icon icon={FileText} size="xsm" />,
|
|
40
|
+
isSelected,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const FILE_TREE: TreeListItemData[] = [
|
|
44
|
+
folder('src', [
|
|
45
|
+
folder('components', [
|
|
46
|
+
file('AppShell.tsx', true),
|
|
47
|
+
file('TopNav.tsx'),
|
|
48
|
+
file('SideNav.tsx'),
|
|
49
|
+
]),
|
|
50
|
+
folder('hooks', [file('useTheme.ts'), file('useResizable.ts')]),
|
|
51
|
+
file('index.tsx'),
|
|
52
|
+
file('App.tsx'),
|
|
53
|
+
]),
|
|
54
|
+
folder('public', [file('favicon.ico'), file('robots.txt')], false),
|
|
55
|
+
file('package.json'),
|
|
56
|
+
file('tsconfig.json'),
|
|
57
|
+
file('README.md'),
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
// Each menu is split into groups; groups are separated by a divider.
|
|
61
|
+
// `[label, shortcut]` — the shortcut renders as a single combined Kbd
|
|
62
|
+
// (e.g. ⌘N); an empty shortcut renders no Kbd.
|
|
63
|
+
type MenuEntry = [label: string, shortcut: string];
|
|
64
|
+
|
|
65
|
+
// Wide enough that label + Kbd shortcut never clip at the menu edge —
|
|
66
|
+
// "Previous Tab ⌃⇧⇥" is the widest row and touched the border at 280.
|
|
67
|
+
const MENU_WIDTH = 300;
|
|
68
|
+
|
|
69
|
+
const MENUS: {label: string; groups: MenuEntry[][]}[] = [
|
|
70
|
+
{
|
|
71
|
+
label: 'File',
|
|
72
|
+
groups: [
|
|
73
|
+
[
|
|
74
|
+
['New File', '⌘N'],
|
|
75
|
+
['New Window', '⇧⌘N'],
|
|
76
|
+
],
|
|
77
|
+
[
|
|
78
|
+
['Open...', '⌘O'],
|
|
79
|
+
['Save', '⌘S'],
|
|
80
|
+
['Save As...', '⇧⌘S'],
|
|
81
|
+
],
|
|
82
|
+
[['Close Editor', '⌘W']],
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
label: 'Edit',
|
|
87
|
+
groups: [
|
|
88
|
+
[
|
|
89
|
+
['Undo', '⌘Z'],
|
|
90
|
+
['Redo', '⇧⌘Z'],
|
|
91
|
+
],
|
|
92
|
+
[
|
|
93
|
+
['Cut', '⌘X'],
|
|
94
|
+
['Copy', '⌘C'],
|
|
95
|
+
['Paste', '⌘V'],
|
|
96
|
+
],
|
|
97
|
+
[['Find', '⌘F']],
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
label: 'View',
|
|
102
|
+
groups: [
|
|
103
|
+
[['Command Palette', '⇧⌘P']],
|
|
104
|
+
[
|
|
105
|
+
['Explorer', '⇧⌘E'],
|
|
106
|
+
['Search', '⇧⌘F'],
|
|
107
|
+
],
|
|
108
|
+
[
|
|
109
|
+
['Toggle Terminal', '⌃`'],
|
|
110
|
+
['Zen Mode', '⌘K'],
|
|
111
|
+
],
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
label: 'Window',
|
|
116
|
+
groups: [
|
|
117
|
+
[
|
|
118
|
+
['Minimize', '⌘M'],
|
|
119
|
+
['Zoom', ''],
|
|
120
|
+
],
|
|
121
|
+
[
|
|
122
|
+
['Next Tab', '⌃⇥'],
|
|
123
|
+
['Previous Tab', '⌃⇧⇥'],
|
|
124
|
+
],
|
|
125
|
+
[['Bring All to Front', '']],
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
label: 'Help',
|
|
130
|
+
groups: [
|
|
131
|
+
[
|
|
132
|
+
['Documentation', ''],
|
|
133
|
+
['Release Notes', ''],
|
|
134
|
+
['Report Issue', ''],
|
|
135
|
+
['About', ''],
|
|
136
|
+
],
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const CODE_LINES = [
|
|
142
|
+
'38%',
|
|
143
|
+
'62%',
|
|
144
|
+
'54%',
|
|
145
|
+
'0%',
|
|
146
|
+
'46%',
|
|
147
|
+
'70%',
|
|
148
|
+
'58%',
|
|
149
|
+
'34%',
|
|
150
|
+
'0%',
|
|
151
|
+
'50%',
|
|
152
|
+
'66%',
|
|
153
|
+
'42%',
|
|
154
|
+
'60%',
|
|
155
|
+
'28%',
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
const EDITOR_TABS = ['AppShell.tsx', 'TopNav.tsx', 'theme.ts'];
|
|
159
|
+
|
|
160
|
+
const COMMANDS = [
|
|
161
|
+
{id: 'new-file', label: 'New File'},
|
|
162
|
+
{id: 'open-file', label: 'Open File…'},
|
|
163
|
+
{id: 'save-all', label: 'Save All'},
|
|
164
|
+
{id: 'find-in-files', label: 'Find in Files'},
|
|
165
|
+
{id: 'toggle-terminal', label: 'Toggle Terminal'},
|
|
166
|
+
{id: 'go-to-symbol', label: 'Go to Symbol…'},
|
|
167
|
+
{id: 'appshell', label: 'AppShell.tsx'},
|
|
168
|
+
{id: 'topnav', label: 'TopNav.tsx'},
|
|
169
|
+
{id: 'sidenav', label: 'SideNav.tsx'},
|
|
170
|
+
{id: 'use-theme', label: 'useTheme.ts'},
|
|
171
|
+
{id: 'theme', label: 'theme.ts'},
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
export default function ShellNav() {
|
|
175
|
+
const [isPaletteOpen, setIsPaletteOpen] = useState(false);
|
|
176
|
+
const searchSource = useMemo(() => createStaticSource(COMMANDS), []);
|
|
177
|
+
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
180
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
|
181
|
+
e.preventDefault();
|
|
182
|
+
setIsPaletteOpen(true);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
186
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
187
|
+
}, []);
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<>
|
|
191
|
+
<AppShell
|
|
192
|
+
contentPadding={0}
|
|
193
|
+
topNav={
|
|
194
|
+
<TopNav
|
|
195
|
+
label="Dracula Studio menu bar"
|
|
196
|
+
startContent={
|
|
197
|
+
<>
|
|
198
|
+
{MENUS.map(menu => (
|
|
199
|
+
<DropdownMenu
|
|
200
|
+
key={menu.label}
|
|
201
|
+
button={{label: menu.label, variant: 'ghost', size: 'sm'}}
|
|
202
|
+
hasChevron={false}
|
|
203
|
+
menuWidth={MENU_WIDTH}>
|
|
204
|
+
{menu.groups.map((group, gi) => (
|
|
205
|
+
<Fragment key={gi}>
|
|
206
|
+
{gi > 0 && <Divider />}
|
|
207
|
+
{group.map(([label, shortcut]) => (
|
|
208
|
+
<DropdownMenuItem
|
|
209
|
+
key={label}
|
|
210
|
+
label={label}
|
|
211
|
+
onClick={noop}
|
|
212
|
+
endContent={
|
|
213
|
+
shortcut ? <Kbd keys={shortcut} /> : undefined
|
|
214
|
+
}
|
|
215
|
+
/>
|
|
216
|
+
))}
|
|
217
|
+
</Fragment>
|
|
218
|
+
))}
|
|
219
|
+
</DropdownMenu>
|
|
220
|
+
))}
|
|
221
|
+
</>
|
|
222
|
+
}
|
|
223
|
+
endContent={
|
|
224
|
+
// A real palette trigger, not a lookalike field: the previous
|
|
225
|
+
// TextInput swallowed keystrokes (fixed value, noop onChange)
|
|
226
|
+
// while a wrapper div opened the palette on click. The dead Run
|
|
227
|
+
// and Share buttons go with it — demo chrome must not ship
|
|
228
|
+
// controls that do nothing.
|
|
229
|
+
<Button
|
|
230
|
+
label="Search files and commands"
|
|
231
|
+
variant="secondary"
|
|
232
|
+
size="sm"
|
|
233
|
+
tooltip="Search files and commands (⌘K)"
|
|
234
|
+
icon={<Icon icon={Search} size="sm" />}
|
|
235
|
+
onClick={() => setIsPaletteOpen(true)}
|
|
236
|
+
/>
|
|
237
|
+
}
|
|
238
|
+
/>
|
|
239
|
+
}
|
|
240
|
+
sideNav={
|
|
241
|
+
<SideNav
|
|
242
|
+
resizable={{defaultWidth: 240, minWidth: 180, maxWidth: 400}}>
|
|
243
|
+
<TreeList items={FILE_TREE} density="compact" />
|
|
244
|
+
</SideNav>
|
|
245
|
+
}>
|
|
246
|
+
<Layout
|
|
247
|
+
height="fill"
|
|
248
|
+
header={
|
|
249
|
+
<LayoutHeader hasDivider padding={6}>
|
|
250
|
+
<HStack gap={2}>
|
|
251
|
+
{EDITOR_TABS.map(tab => (
|
|
252
|
+
<Card
|
|
253
|
+
key={tab}
|
|
254
|
+
variant="muted"
|
|
255
|
+
padding={0}
|
|
256
|
+
width={132}
|
|
257
|
+
height={36}
|
|
258
|
+
/>
|
|
259
|
+
))}
|
|
260
|
+
</HStack>
|
|
261
|
+
</LayoutHeader>
|
|
262
|
+
}
|
|
263
|
+
content={
|
|
264
|
+
<LayoutContent padding={6}>
|
|
265
|
+
<VStack gap={2}>
|
|
266
|
+
{CODE_LINES.map((width, i) =>
|
|
267
|
+
width === '0%' ? (
|
|
268
|
+
<Card
|
|
269
|
+
key={i}
|
|
270
|
+
variant="muted"
|
|
271
|
+
padding={0}
|
|
272
|
+
width={1}
|
|
273
|
+
height={14}
|
|
274
|
+
/>
|
|
275
|
+
) : (
|
|
276
|
+
<HStack key={i} gap={3} vAlign="center">
|
|
277
|
+
<Card
|
|
278
|
+
variant="muted"
|
|
279
|
+
padding={0}
|
|
280
|
+
width={20}
|
|
281
|
+
height={14}
|
|
282
|
+
/>
|
|
283
|
+
<Card
|
|
284
|
+
variant="muted"
|
|
285
|
+
padding={0}
|
|
286
|
+
width={width}
|
|
287
|
+
height={14}
|
|
288
|
+
/>
|
|
289
|
+
</HStack>
|
|
290
|
+
),
|
|
291
|
+
)}
|
|
292
|
+
</VStack>
|
|
293
|
+
</LayoutContent>
|
|
294
|
+
}
|
|
295
|
+
/>
|
|
296
|
+
</AppShell>
|
|
297
|
+
<CommandPalette
|
|
298
|
+
isOpen={isPaletteOpen}
|
|
299
|
+
onOpenChange={setIsPaletteOpen}
|
|
300
|
+
searchSource={searchSource}
|
|
301
|
+
label="Search files and commands"
|
|
302
|
+
onValueChange={() => setIsPaletteOpen(false)}
|
|
303
|
+
/>
|
|
304
|
+
</>
|
|
305
|
+
);
|
|
306
|
+
}
|