Skip to content

Commit fbc8ecd

Browse files
authored
refactor(ui): move sidebar components to ui package for reusability (#246)
* chore(common): remove old constitution bak * refactor(ui): move sidebar components to ui package for reusability - Add SidebarButton, SidebarLayout, SidebarSection to @openzeppelin/ui-builder-ui - Refactor builder's AppSidebar to use generic components from ui package - Enable sidebar reuse across other projects like role-manager
1 parent 5efd65a commit fbc8ecd

File tree

11 files changed

+235
-273
lines changed

11 files changed

+235
-273
lines changed

.changeset/move-sidebar-to-ui.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openzeppelin/ui-builder-ui': minor
3+
---
4+
5+
Add reusable sidebar components (SidebarButton, SidebarLayout, SidebarSection) to enable sidebar reuse across projects

.specify/memory/constitution_bak.md

Lines changed: 0 additions & 108 deletions
This file was deleted.
Lines changed: 24 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState } from 'react';
22

3-
import { cn } from '@openzeppelin/ui-builder-utils';
3+
import { SidebarLayout } from '@openzeppelin/ui-builder-ui';
44

55
import ContractUIImportDialog from '../ContractUIs/ContractUIImportDialog';
66
import SidebarContent from './SidebarContent';
@@ -21,7 +21,8 @@ interface AppSidebarProps {
2121
}
2222

2323
/**
24-
* Main application sidebar component with logo, actions, and saved Contract UIs
24+
* Main application sidebar component with logo, actions, and saved Contract UIs.
25+
* Uses the generic SidebarLayout from @openzeppelin/ui-builder-ui.
2526
*/
2627
export default function AppSidebar({
2728
className,
@@ -35,110 +36,35 @@ export default function AppSidebar({
3536
}: AppSidebarProps) {
3637
const [showImportDialog, setShowImportDialog] = useState(false);
3738

38-
/** Shared sidebar scrollable content wrapper */
39-
const SidebarBody = ({
40-
paddingClass,
41-
gapClass = 'gap-12',
42-
onLoadContractUiHandler,
43-
}: {
44-
paddingClass: string;
45-
gapClass?: string;
46-
onLoadContractUiHandler: (id: string) => void;
47-
}) => (
48-
<div className={cn('flex-1 overflow-y-auto', paddingClass)}>
49-
<SidebarContent
50-
onCreateNew={onCreateNew}
51-
onShowImportDialog={() => setShowImportDialog(true)}
52-
isInNewUIMode={isInNewUIMode}
53-
onLoadContractUI={onLoadContractUiHandler}
54-
onResetAfterDelete={onResetAfterDelete}
55-
currentLoadedConfigurationId={currentLoadedConfigurationId}
56-
gapClass={gapClass}
57-
/>
58-
</div>
59-
);
39+
const handleLoadContractUI = (id: string) => {
40+
// Close mobile sidebar when loading a contract UI
41+
if (onOpenChange) {
42+
onOpenChange(false);
43+
}
44+
onLoadContractUI?.(id);
45+
};
6046

6147
return (
6248
<>
63-
{/* Sidebar */}
64-
<div
65-
className={cn(
66-
// TODO: Replace hard-coded sidebar background with OpenZeppelin theme
67-
// Should use semantic token like 'bg-sidebar-background'
68-
'fixed left-0 top-0 z-40 h-full w-[289px] bg-[rgba(245,245,245,0.31)] hidden xl:flex xl:flex-col',
69-
className
70-
)}
49+
<SidebarLayout
50+
className={className}
51+
header={<SidebarLogo />}
52+
footer={<SidebarNavIcons />}
53+
mobileOpen={open}
54+
onMobileOpenChange={onOpenChange}
7155
>
72-
{/* Fixed Header - Logo */}
73-
<div className="flex-shrink-0 px-8 pt-12">
74-
<SidebarLogo />
75-
</div>
76-
77-
{/* Scrollable Content Area */}
78-
<SidebarBody
79-
paddingClass="px-8 pb-24"
80-
onLoadContractUiHandler={(id) => onLoadContractUI?.(id)}
56+
<SidebarContent
57+
onCreateNew={onCreateNew}
58+
onShowImportDialog={() => setShowImportDialog(true)}
59+
isInNewUIMode={isInNewUIMode}
60+
onLoadContractUI={handleLoadContractUI}
61+
onResetAfterDelete={onResetAfterDelete}
62+
currentLoadedConfigurationId={currentLoadedConfigurationId}
8163
/>
82-
83-
{/* Fixed footer icons */}
84-
<div className="pointer-events-auto sticky bottom-0 px-8 py-4">
85-
<SidebarNavIcons />
86-
</div>
87-
</div>
64+
</SidebarLayout>
8865

8966
{/* Import Dialog */}
9067
<ContractUIImportDialog open={showImportDialog} onOpenChange={setShowImportDialog} />
91-
92-
{/* Spacer to push content (desktop only) */}
93-
<div className="hidden xl:block w-[289px]" />
94-
95-
{/* Mobile slide-over */}
96-
{typeof open === 'boolean' && onOpenChange && (
97-
<div
98-
className={cn(
99-
'xl:hidden fixed inset-0 z-50',
100-
open ? 'pointer-events-auto' : 'pointer-events-none'
101-
)}
102-
aria-hidden={!open}
103-
>
104-
{/* Backdrop */}
105-
<div
106-
className={cn(
107-
'absolute inset-0 bg-black/40 transition-opacity',
108-
open ? 'opacity-100' : 'opacity-0'
109-
)}
110-
onClick={() => onOpenChange(false)}
111-
/>
112-
{/* Panel */}
113-
<div
114-
className={cn(
115-
'absolute left-0 top-0 h-full w-[85%] max-w-[320px] bg-[rgba(245,245,245,0.98)] shadow-xl transition-transform',
116-
open ? 'translate-x-0' : '-translate-x-full'
117-
)}
118-
role="dialog"
119-
aria-modal="true"
120-
aria-label="Menu"
121-
>
122-
<div className="flex h-full flex-col">
123-
<div className="flex-shrink-0 px-6 pt-10 pb-4">
124-
<SidebarLogo />
125-
</div>
126-
<SidebarBody
127-
paddingClass="px-6 pb-20"
128-
gapClass="gap-10"
129-
onLoadContractUiHandler={(id) => {
130-
onOpenChange(false);
131-
onLoadContractUI?.(id);
132-
}}
133-
/>
134-
{/* Mobile fixed footer icons */}
135-
<div className="px-6 py-4">
136-
<SidebarNavIcons />
137-
</div>
138-
</div>
139-
</div>
140-
</div>
141-
)}
14268
</>
14369
);
14470
}

packages/builder/src/components/Sidebar/AppSidebar/ContractUIsSection.tsx

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { SidebarSection } from '@openzeppelin/ui-builder-ui';
2+
13
import { useContractUIStorage } from '../../../contexts/useContractUIStorage';
24
import ContractUIsList from '../ContractUIs/ContractUIsList';
35

@@ -26,22 +28,12 @@ export default function ContractUIsSection({
2628
}
2729

2830
return (
29-
<div className="flex flex-col w-full flex-1">
30-
{/* Section Header */}
31-
<div className="flex items-center justify-between mb-1">
32-
{/* TODO: Replace hard-coded text color with OpenZeppelin theme */}
33-
{/* Should use semantic token like 'text-sidebar-section-header' */}
34-
<div className="text-[#5e5e5e] text-xs font-semibold leading-4">Contract UIs</div>
35-
</div>
36-
37-
{/* List Container */}
38-
<div className="flex-1 overflow-hidden">
39-
<ContractUIsList
40-
onLoadContractUI={onLoadContractUI}
41-
onResetAfterDelete={onResetAfterDelete}
42-
currentLoadedConfigurationId={currentLoadedConfigurationId}
43-
/>
44-
</div>
45-
</div>
31+
<SidebarSection title="Contract UIs" grow>
32+
<ContractUIsList
33+
onLoadContractUI={onLoadContractUI}
34+
onResetAfterDelete={onResetAfterDelete}
35+
currentLoadedConfigurationId={currentLoadedConfigurationId}
36+
/>
37+
</SidebarSection>
4638
);
4739
}

packages/builder/src/components/Sidebar/AppSidebar/MainActions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import {
66
SquarePen,
77
} from 'lucide-react';
88

9+
import { SidebarButton } from '@openzeppelin/ui-builder-ui';
910
import { cn } from '@openzeppelin/ui-builder-utils';
1011

1112
import { useContractUIStorage } from '../../../contexts/useContractUIStorage';
1213
import { useAnalytics } from '../../../hooks/useAnalytics';
1314
import { recordHasMeaningfulContent } from '../../UIBuilder/utils/meaningfulContent';
14-
import SidebarButton from './SidebarButton';
1515

1616
interface MainActionsProps {
1717
onCreateNew?: () => void;
Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import { SidebarButton, SidebarSection } from '@openzeppelin/ui-builder-ui';
12
import { appConfigService } from '@openzeppelin/ui-builder-utils';
23

34
import ContractsWizardIconSvg from '../../../assets/icons/contracts-wizard-icon.svg';
45
import { DevToolsDropdown } from '../../Common/DevToolsDropdown';
5-
import SidebarButton from './SidebarButton';
66

77
/**
88
* Other Tools section component for the sidebar
@@ -12,27 +12,22 @@ export default function OtherToolsSection() {
1212
const showDevTools = appConfigService.isFeatureEnabled('show_dev_tools');
1313

1414
return (
15-
<div className="flex flex-col w-full">
16-
{/* TODO: Replace hard-coded text color with OpenZeppelin theme */}
17-
{/* Should use semantic token like 'text-sidebar-section-header' */}
18-
<div className="text-[#5e5e5e] text-xs font-semibold leading-4 mb-1">Other Tools</div>
19-
<div className="flex flex-col">
20-
<SidebarButton
21-
icon={<img src={ContractsWizardIconSvg} alt="Contracts Wizard" className="size-4" />}
22-
href="https://wizard.openzeppelin.com/"
23-
target="_blank"
24-
rel="noopener noreferrer"
25-
>
26-
Contracts Wizard
27-
</SidebarButton>
15+
<SidebarSection title="Other Tools">
16+
<SidebarButton
17+
icon={<img src={ContractsWizardIconSvg} alt="Contracts Wizard" className="size-4" />}
18+
href="https://wizard.openzeppelin.com/"
19+
target="_blank"
20+
rel="noopener noreferrer"
21+
>
22+
Contracts Wizard
23+
</SidebarButton>
2824

29-
{/* Dev Tools - Only shown when feature flag is enabled */}
30-
{showDevTools && (
31-
<div className="relative">
32-
<DevToolsDropdown />
33-
</div>
34-
)}
35-
</div>
36-
</div>
25+
{/* Dev Tools - Only shown when feature flag is enabled */}
26+
{showDevTools && (
27+
<div className="relative">
28+
<DevToolsDropdown />
29+
</div>
30+
)}
31+
</SidebarSection>
3732
);
3833
}

packages/ui/src/components/ui/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export * from './network-status-badge';
1818
export * from './progress';
1919
export * from './radio-group';
2020
export * from './select';
21+
export * from './sidebar';
2122
export * from './tabs';
2223
export * from './textarea';
2324
export * from './tooltip';

0 commit comments

Comments
 (0)