@leanspec/ui 0.2.14 → 0.2.15-dev.21025278490
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/bin/leanspec-ui.js +191 -0
- package/dist/assets/_baseUniq-CRqreL7N.js +1 -0
- package/dist/assets/arc-DMhx9AJT.js +1 -0
- package/dist/assets/architectureDiagram-VXUJARFQ-DM0L0YzO.js +36 -0
- package/dist/assets/blockDiagram-VD42YOAC-DHQXDHsD.js +122 -0
- package/dist/assets/c4Diagram-YG6GDRKO-0L7o2gpH.js +10 -0
- package/dist/assets/channel-2tOl0nAZ.js +1 -0
- package/dist/assets/chunk-4BX2VUAB-CwFT-Uaj.js +1 -0
- package/dist/assets/chunk-55IACEB6-CjvuUHHG.js +1 -0
- package/dist/assets/chunk-B4BG7PRW-BRJBysMK.js +165 -0
- package/dist/assets/chunk-DI55MBZ5-BnNEeoaA.js +220 -0
- package/dist/assets/chunk-FMBD7UC4-BK2l30pm.js +15 -0
- package/dist/assets/chunk-QN33PNHL-BN_cZkCU.js +1 -0
- package/dist/assets/chunk-QZHKN3VN-Brc3Yrub.js +1 -0
- package/dist/assets/chunk-TZMSLE5B-D2zzpLfO.js +1 -0
- package/dist/assets/classDiagram-2ON5EDUG-BB9CSNmS.js +1 -0
- package/dist/assets/classDiagram-v2-WZHVMYZB-BB9CSNmS.js +1 -0
- package/dist/assets/clone-BjxVFtyI.js +1 -0
- package/dist/assets/core-DV6XEvTN.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-CLJgM3XR.js +1 -0
- package/dist/assets/cytoscape.esm-5J0xJHOV.js +321 -0
- package/dist/assets/dagre-6UL2VRFP-_IFvBJKJ.js +4 -0
- package/dist/assets/diagram-PSM6KHXK--83HIYSQ.js +24 -0
- package/dist/assets/diagram-QEK2KX5R-6jAWnCnZ.js +43 -0
- package/dist/assets/diagram-S2PKOQOG-D5pwHvjZ.js +24 -0
- package/dist/assets/erDiagram-Q2GNP2WA-B4FV3mTd.js +60 -0
- package/dist/assets/flowDiagram-NV44I4VS-mtD2kF4M.js +162 -0
- package/dist/assets/ganttDiagram-JELNMOA3-BKALgqTK.js +267 -0
- package/dist/assets/gitGraphDiagram-NY62KEGX-Bd7r0pAf.js +65 -0
- package/dist/assets/graph-B2rEI7cK.js +1 -0
- package/dist/assets/index-Bekv_o1t.css +1 -0
- package/dist/assets/index-DSRxU-E5.js +389 -0
- package/dist/assets/infoDiagram-WHAUD3N6--nJOBKqh.js +2 -0
- package/dist/assets/journeyDiagram-XKPGCS4Q-BzGutKN3.js +139 -0
- package/dist/assets/kanban-definition-3W4ZIXB7-DyQO17vq.js +89 -0
- package/dist/assets/katex-XbL3y5x-.js +261 -0
- package/dist/assets/layout-iCSHU015.js +1 -0
- package/dist/assets/min-BK_AIJdo.js +1 -0
- package/dist/assets/mindmap-definition-VGOIOE7T-BZMj_6zo.js +68 -0
- package/dist/assets/pieDiagram-ADFJNKIX-CkAGsq9p.js +30 -0
- package/dist/assets/quadrantDiagram-AYHSOK5B-CWa93px1.js +7 -0
- package/dist/assets/requirementDiagram-UZGBJVZJ-CufFVR8c.js +64 -0
- package/dist/assets/sankeyDiagram-TZEHDZUN-BEPgVgU4.js +10 -0
- package/dist/assets/sequenceDiagram-WL72ISMW-BkdBWhel.js +145 -0
- package/dist/assets/stateDiagram-FKZM4ZOC-D5T73yx0.js +1 -0
- package/dist/assets/stateDiagram-v2-4FDKWEC3-9hJWG2n6.js +1 -0
- package/dist/assets/timeline-definition-IT6M3QCI-CX7kTdU2.js +61 -0
- package/dist/assets/treemap-KMMF4GRG-ftWCQ9lJ.js +128 -0
- package/dist/assets/xychartDiagram-PRI3JC2R-Ngrels4n.js +7 -0
- package/{index.html → dist/index.html} +2 -1
- package/package.json +12 -2
- package/eslint.config.js +0 -23
- package/package.json.backup +0 -83
- package/postcss.config.js +0 -6
- package/src/App.css +0 -42
- package/src/App.tsx +0 -17
- package/src/assets/react.svg +0 -1
- package/src/components/LanguageSwitcher.tsx +0 -67
- package/src/components/Layout.tsx +0 -88
- package/src/components/MainSidebar.tsx +0 -163
- package/src/components/MermaidDiagram.tsx +0 -85
- package/src/components/MinimalLayout.tsx +0 -51
- package/src/components/Navigation.tsx +0 -254
- package/src/components/PriorityBadge.tsx +0 -59
- package/src/components/ProjectSwitcher.tsx +0 -222
- package/src/components/QuickSearch.tsx +0 -225
- package/src/components/RootRedirect.tsx +0 -40
- package/src/components/SpecDetailLayout.context.ts +0 -10
- package/src/components/SpecDetailLayout.tsx +0 -14
- package/src/components/SpecsNavSidebar.tsx +0 -615
- package/src/components/StatusBadge.tsx +0 -59
- package/src/components/ThemeToggle.tsx +0 -25
- package/src/components/Tooltip.tsx +0 -29
- package/src/components/context/ContextClient.tsx +0 -471
- package/src/components/context/ContextFileDetail.tsx +0 -163
- package/src/components/dashboard/ActivityItem.tsx +0 -36
- package/src/components/dashboard/DashboardClient.tsx +0 -218
- package/src/components/dashboard/SpecListItem.tsx +0 -58
- package/src/components/dashboard/StatCard.tsx +0 -52
- package/src/components/dependencies/SpecNode.tsx +0 -128
- package/src/components/dependencies/SpecSidebar.tsx +0 -256
- package/src/components/dependencies/constants.ts +0 -25
- package/src/components/dependencies/types.ts +0 -38
- package/src/components/dependencies/utils.ts +0 -261
- package/src/components/metadata-editors/PriorityEditor.tsx +0 -89
- package/src/components/metadata-editors/StatusEditor.tsx +0 -85
- package/src/components/metadata-editors/TagsEditor.tsx +0 -207
- package/src/components/projects/CreateProjectDialog.tsx +0 -162
- package/src/components/projects/DirectoryPicker.tsx +0 -182
- package/src/components/shared/BackToTop.tsx +0 -39
- package/src/components/shared/ColorPicker.tsx +0 -68
- package/src/components/shared/EmptyState.tsx +0 -35
- package/src/components/shared/ErrorBoundary.tsx +0 -79
- package/src/components/shared/PageHeader.tsx +0 -23
- package/src/components/shared/PageTransition.tsx +0 -40
- package/src/components/shared/ProjectAvatar.tsx +0 -107
- package/src/components/shared/Skeletons.tsx +0 -184
- package/src/components/spec-detail/EditableMetadata.tsx +0 -129
- package/src/components/spec-detail/MarkdownRenderer.tsx +0 -47
- package/src/components/spec-detail/TableOfContents.tsx +0 -150
- package/src/components/specs/BoardView.tsx +0 -204
- package/src/components/specs/ListView.tsx +0 -62
- package/src/components/specs/SpecsFilters.tsx +0 -190
- package/src/contexts/KeyboardShortcutsContext.tsx +0 -95
- package/src/contexts/LayoutContext.tsx +0 -45
- package/src/contexts/ProjectContext.tsx +0 -163
- package/src/contexts/ThemeContext.tsx +0 -90
- package/src/contexts/index.ts +0 -7
- package/src/hooks/useKeyboardShortcuts.ts +0 -87
- package/src/index.css +0 -624
- package/src/lib/api.ts +0 -72
- package/src/lib/backend-adapter.ts +0 -382
- package/src/lib/date-utils.ts +0 -122
- package/src/lib/i18n.test.ts +0 -57
- package/src/lib/i18n.ts +0 -51
- package/src/lib/markdown-utils.ts +0 -38
- package/src/lib/sub-spec-utils.ts +0 -166
- package/src/lib/utils.ts +0 -6
- package/src/locales/en/common.json +0 -660
- package/src/locales/en/errors.json +0 -20
- package/src/locales/en/help.json +0 -8
- package/src/locales/zh-CN/common.json +0 -660
- package/src/locales/zh-CN/errors.json +0 -20
- package/src/locales/zh-CN/help.json +0 -8
- package/src/main.tsx +0 -12
- package/src/pages/ContextPage.tsx +0 -111
- package/src/pages/DashboardPage.tsx +0 -97
- package/src/pages/DependenciesPage.tsx +0 -881
- package/src/pages/ProjectsPage.tsx +0 -432
- package/src/pages/SpecDetailPage.tsx +0 -592
- package/src/pages/SpecsPage.tsx +0 -319
- package/src/pages/StatsPage.tsx +0 -307
- package/src/router/projectRoutes.tsx +0 -36
- package/src/router.tsx +0 -33
- package/src/test/setup.ts +0 -39
- package/src/types/api.ts +0 -185
- package/tailwind.config.ts +0 -57
- package/tsconfig.app.json +0 -29
- package/tsconfig.json +0 -7
- package/tsconfig.node.json +0 -26
- package/tsconfig.tsbuildinfo +0 -1
- package/vite.config.ts +0 -27
- package/vitest.config.ts +0 -18
- /package/{public → dist}/favicon.ico +0 -0
- /package/{public → dist}/github-mark-white.svg +0 -0
- /package/{public → dist}/github-mark.svg +0 -0
- /package/{public → dist}/logo-dark-bg.svg +0 -0
- /package/{public → dist}/logo-with-bg.svg +0 -0
- /package/{public → dist}/logo.svg +0 -0
- /package/{public → dist}/vite.svg +0 -0
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import { useNavigate, useParams } from 'react-router-dom';
|
|
3
|
-
import Fuse from 'fuse.js';
|
|
4
|
-
import { Clock, FileText, Search, Tag } from 'lucide-react';
|
|
5
|
-
import {
|
|
6
|
-
CommandDialog,
|
|
7
|
-
CommandEmpty,
|
|
8
|
-
CommandGroup,
|
|
9
|
-
CommandInput,
|
|
10
|
-
CommandItem,
|
|
11
|
-
CommandList,
|
|
12
|
-
CommandSeparator,
|
|
13
|
-
Button,
|
|
14
|
-
} from '@leanspec/ui-components';
|
|
15
|
-
import { StatusBadge } from './StatusBadge';
|
|
16
|
-
import { PriorityBadge } from './PriorityBadge';
|
|
17
|
-
import { useTranslation } from 'react-i18next';
|
|
18
|
-
import type { Spec } from '../types/api';
|
|
19
|
-
import { api } from '../lib/api';
|
|
20
|
-
|
|
21
|
-
const RECENT_STORAGE_KEY = 'leanspec-recent-searches';
|
|
22
|
-
|
|
23
|
-
const formatSpecNumber = (specNumber: number | null) =>
|
|
24
|
-
specNumber != null ? specNumber.toString().padStart(3, '0') : null;
|
|
25
|
-
|
|
26
|
-
export function QuickSearch() {
|
|
27
|
-
const navigate = useNavigate();
|
|
28
|
-
const { projectId } = useParams<{ projectId: string }>();
|
|
29
|
-
const basePath = projectId ? `/projects/${projectId}` : '/projects/default';
|
|
30
|
-
const [open, setOpen] = useState(false);
|
|
31
|
-
const [search, setSearch] = useState('');
|
|
32
|
-
const [recentSearches, setRecentSearches] = useState<string[]>([]);
|
|
33
|
-
const [specs, setSpecs] = useState<Spec[]>([]);
|
|
34
|
-
const { t } = useTranslation('common');
|
|
35
|
-
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
api.getSpecs()
|
|
38
|
-
.then((data) => setSpecs(data))
|
|
39
|
-
.catch(() => {
|
|
40
|
-
// Quick search is best-effort; ignore failures
|
|
41
|
-
});
|
|
42
|
-
}, []);
|
|
43
|
-
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
const stored = localStorage.getItem(RECENT_STORAGE_KEY);
|
|
46
|
-
if (!stored) return;
|
|
47
|
-
try {
|
|
48
|
-
const parsed = JSON.parse(stored);
|
|
49
|
-
if (Array.isArray(parsed)) {
|
|
50
|
-
setRecentSearches(parsed.filter((item) => typeof item === 'string'));
|
|
51
|
-
}
|
|
52
|
-
} catch {
|
|
53
|
-
setRecentSearches([]);
|
|
54
|
-
}
|
|
55
|
-
}, []);
|
|
56
|
-
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
const handler = (e: KeyboardEvent) => {
|
|
59
|
-
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
|
60
|
-
e.preventDefault();
|
|
61
|
-
setOpen((prev) => !prev);
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
document.addEventListener('keydown', handler);
|
|
66
|
-
return () => document.removeEventListener('keydown', handler);
|
|
67
|
-
}, []);
|
|
68
|
-
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
const handler = (event: Event) => {
|
|
71
|
-
const desktopEvent = event as CustomEvent<{ action?: string }>;
|
|
72
|
-
if (desktopEvent.detail?.action === 'desktop://menu-find') {
|
|
73
|
-
setOpen(true);
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
window.addEventListener('leanspec:desktop-menu', handler as EventListener);
|
|
78
|
-
return () => window.removeEventListener('leanspec:desktop-menu', handler as EventListener);
|
|
79
|
-
}, []);
|
|
80
|
-
|
|
81
|
-
const fuse = useMemo(
|
|
82
|
-
() =>
|
|
83
|
-
new Fuse(specs, {
|
|
84
|
-
keys: [
|
|
85
|
-
{ name: 'title', weight: 2 },
|
|
86
|
-
{ name: 'specNumber', weight: 1.5 },
|
|
87
|
-
{ name: 'specName', weight: 1 },
|
|
88
|
-
{ name: 'tags', weight: 0.5 },
|
|
89
|
-
],
|
|
90
|
-
threshold: 0.4,
|
|
91
|
-
includeScore: true,
|
|
92
|
-
minMatchCharLength: 2,
|
|
93
|
-
}),
|
|
94
|
-
[specs]
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
const results = useMemo(() => {
|
|
98
|
-
if (!search) return specs.slice(0, 8);
|
|
99
|
-
return fuse.search(search).map((result) => result.item).slice(0, 12);
|
|
100
|
-
}, [search, fuse, specs]);
|
|
101
|
-
|
|
102
|
-
const tagSuggestions = useMemo(() => {
|
|
103
|
-
const allTags = Array.from(new Set(specs.flatMap((s) => s.tags || [])));
|
|
104
|
-
if (!search) return allTags.slice(0, 5);
|
|
105
|
-
const query = search.toLowerCase();
|
|
106
|
-
return allTags
|
|
107
|
-
.filter((tag) => tag.toLowerCase().includes(query))
|
|
108
|
-
.slice(0, 5);
|
|
109
|
-
}, [specs, search]);
|
|
110
|
-
|
|
111
|
-
const persistRecentSearches = (entries: string[]) => {
|
|
112
|
-
setRecentSearches(entries);
|
|
113
|
-
localStorage.setItem(RECENT_STORAGE_KEY, JSON.stringify(entries));
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const handleSelect = (spec: Spec) => {
|
|
117
|
-
const label = spec.title || spec.specName;
|
|
118
|
-
const next = [label, ...recentSearches.filter((item) => item !== label)].slice(0, 5);
|
|
119
|
-
persistRecentSearches(next);
|
|
120
|
-
setOpen(false);
|
|
121
|
-
setSearch('');
|
|
122
|
-
navigate(`${basePath}/specs/${spec.specName}`);
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const handleTagSelect = (tag: string) => {
|
|
126
|
-
navigate(`${basePath}/specs?tag=${encodeURIComponent(tag)}`);
|
|
127
|
-
setOpen(false);
|
|
128
|
-
setSearch('');
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<>
|
|
133
|
-
<Button
|
|
134
|
-
onClick={() => setOpen(true)}
|
|
135
|
-
variant="outline"
|
|
136
|
-
size="sm"
|
|
137
|
-
className="gap-2 text-muted-foreground hover:text-foreground"
|
|
138
|
-
aria-label={t('quickSearch.open')}
|
|
139
|
-
>
|
|
140
|
-
<Search className="h-4 w-4" />
|
|
141
|
-
<span className="hidden sm:inline">{t('quickSearch.button')}</span>
|
|
142
|
-
<kbd className="hidden md:inline-flex pointer-events-none h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100">
|
|
143
|
-
<span className="text-xs">⌘</span>K
|
|
144
|
-
</kbd>
|
|
145
|
-
</Button>
|
|
146
|
-
|
|
147
|
-
<CommandDialog
|
|
148
|
-
open={open}
|
|
149
|
-
onOpenChange={(value) => {
|
|
150
|
-
setOpen(value);
|
|
151
|
-
if (!value) setSearch('');
|
|
152
|
-
}}
|
|
153
|
-
>
|
|
154
|
-
<CommandInput
|
|
155
|
-
placeholder={t('quickSearch.placeholder')}
|
|
156
|
-
value={search}
|
|
157
|
-
onValueChange={setSearch}
|
|
158
|
-
/>
|
|
159
|
-
<CommandList>
|
|
160
|
-
<CommandEmpty>{t('search.noResults')}</CommandEmpty>
|
|
161
|
-
|
|
162
|
-
{!search && recentSearches.length > 0 && (
|
|
163
|
-
<CommandGroup heading={t('quickSearch.recentSearches')}>
|
|
164
|
-
{recentSearches.map((recent) => (
|
|
165
|
-
<CommandItem
|
|
166
|
-
key={recent}
|
|
167
|
-
value={recent}
|
|
168
|
-
onSelect={() => setSearch(recent)}
|
|
169
|
-
>
|
|
170
|
-
<Clock className="mr-2 h-4 w-4" />
|
|
171
|
-
{recent}
|
|
172
|
-
</CommandItem>
|
|
173
|
-
))}
|
|
174
|
-
</CommandGroup>
|
|
175
|
-
)}
|
|
176
|
-
|
|
177
|
-
<CommandGroup heading={t('spec.specs')}>
|
|
178
|
-
{results.map((spec) => {
|
|
179
|
-
const specNumber = formatSpecNumber(spec.specNumber ?? null);
|
|
180
|
-
const label = spec.title || spec.specName;
|
|
181
|
-
return (
|
|
182
|
-
<CommandItem
|
|
183
|
-
key={spec.specName}
|
|
184
|
-
value={`${specNumber ? `#${specNumber}` : ''} ${label}`.trim()}
|
|
185
|
-
onSelect={() => handleSelect(spec)}
|
|
186
|
-
>
|
|
187
|
-
<FileText className="mr-2 h-4 w-4" />
|
|
188
|
-
<div className="flex-1 flex items-center justify-between gap-2 min-w-0">
|
|
189
|
-
<div className="flex-1 min-w-0">
|
|
190
|
-
<div className="flex items-center gap-2">
|
|
191
|
-
{specNumber && (
|
|
192
|
-
<span className="text-xs font-mono text-muted-foreground">#{specNumber}</span>
|
|
193
|
-
)}
|
|
194
|
-
<span className="truncate font-medium">{label}</span>
|
|
195
|
-
</div>
|
|
196
|
-
<div className="text-xs text-muted-foreground truncate">{spec.specName}</div>
|
|
197
|
-
</div>
|
|
198
|
-
<div className="flex items-center gap-1 shrink-0">
|
|
199
|
-
{spec.status && <StatusBadge status={spec.status} />}
|
|
200
|
-
{spec.priority && <PriorityBadge priority={spec.priority} />}
|
|
201
|
-
</div>
|
|
202
|
-
</div>
|
|
203
|
-
</CommandItem>
|
|
204
|
-
);
|
|
205
|
-
})}
|
|
206
|
-
</CommandGroup>
|
|
207
|
-
|
|
208
|
-
{tagSuggestions.length > 0 && (
|
|
209
|
-
<>
|
|
210
|
-
<CommandSeparator />
|
|
211
|
-
<CommandGroup heading={t('quickSearch.filterHeading')}>
|
|
212
|
-
{tagSuggestions.map((tag) => (
|
|
213
|
-
<CommandItem key={tag} value={tag} onSelect={() => handleTagSelect(tag)}>
|
|
214
|
-
<Tag className="mr-2 h-4 w-4" />
|
|
215
|
-
<span className="font-medium">{tag}</span>
|
|
216
|
-
</CommandItem>
|
|
217
|
-
))}
|
|
218
|
-
</CommandGroup>
|
|
219
|
-
</>
|
|
220
|
-
)}
|
|
221
|
-
</CommandList>
|
|
222
|
-
</CommandDialog>
|
|
223
|
-
</>
|
|
224
|
-
);
|
|
225
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { Navigate } from 'react-router-dom';
|
|
2
|
-
import { useProject } from '../contexts';
|
|
3
|
-
|
|
4
|
-
const STORAGE_KEY = 'leanspec-current-project';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Root redirect component that navigates to the appropriate page:
|
|
8
|
-
* - If a project is stored in localStorage and exists, navigate to that project's dashboard
|
|
9
|
-
* - Otherwise, navigate to the projects list page
|
|
10
|
-
*/
|
|
11
|
-
export function RootRedirect() {
|
|
12
|
-
const { currentProject, projects, loading } = useProject();
|
|
13
|
-
|
|
14
|
-
if (loading) {
|
|
15
|
-
// Show nothing while loading to avoid flash of redirect
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// If we have a current project, navigate to it
|
|
20
|
-
if (currentProject?.id) {
|
|
21
|
-
return <Navigate to={`/projects/${currentProject.id}`} replace />;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Check if there's a stored project ID
|
|
25
|
-
const storedId = localStorage.getItem(STORAGE_KEY);
|
|
26
|
-
if (storedId) {
|
|
27
|
-
const storedProject = projects.find((p) => p.id === storedId);
|
|
28
|
-
if (storedProject) {
|
|
29
|
-
return <Navigate to={`/projects/${storedProject.id}`} replace />;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// If there are any projects, navigate to the first one
|
|
34
|
-
if (projects.length > 0) {
|
|
35
|
-
return <Navigate to={`/projects/${projects[0].id}`} replace />;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// No projects, go to projects page
|
|
39
|
-
return <Navigate to="/projects" replace />;
|
|
40
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { useOutletContext } from 'react-router-dom';
|
|
2
|
-
|
|
3
|
-
export type SpecDetailLayoutContext = {
|
|
4
|
-
mobileOpen: boolean;
|
|
5
|
-
setMobileOpen: (open: boolean) => void;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export function useSpecDetailLayoutContext() {
|
|
9
|
-
return useOutletContext<SpecDetailLayoutContext>();
|
|
10
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { Outlet } from 'react-router-dom';
|
|
3
|
-
import { SpecsNavSidebar } from './SpecsNavSidebar';
|
|
4
|
-
|
|
5
|
-
export function SpecDetailLayout() {
|
|
6
|
-
const [mobileOpen, setMobileOpen] = useState(false);
|
|
7
|
-
|
|
8
|
-
return (
|
|
9
|
-
<div className="flex h-full relative">
|
|
10
|
-
<SpecsNavSidebar mobileOpen={mobileOpen} onMobileOpenChange={setMobileOpen} />
|
|
11
|
-
<Outlet context={{ mobileOpen, setMobileOpen }} />
|
|
12
|
-
</div>
|
|
13
|
-
);
|
|
14
|
-
}
|