Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
956 changes: 0 additions & 956 deletions src/components/builder/AdvancedBuilderApp.tsx

This file was deleted.

519 changes: 254 additions & 265 deletions src/components/builder/components/LayerPanel.tsx

Large diffs are not rendered by default.

88 changes: 43 additions & 45 deletions src/components/builder/components/layout/Inspector.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import React, { memo } from 'react';
import { useEditor } from '../../context/EditorContext';
import PropertyPanel from '../PropertyPanel';
import type { PosterConfig } from '../../types';
import { Badge, MousePointer2 } from 'lucide-react';
import clsx from 'clsx';
import SidebarLayout from '../SidebarLayout';
import PanelSwitcher from '../navigation/PanelSwitcher';
import BadgesPanel from '../../panels/right/BadgesPanel';
import SelectionPanel from '../../panels/right/SelectionPanel';

interface Props {
config: PosterConfig;
setConfig: React.Dispatch<React.SetStateAction<PosterConfig>>;
hideTabBar?: boolean;
mode?: InspectorTab;
}

type InspectorTab = 'badges' | 'selection';
const INACTIVE_TAB_HOVER_CLASSES = 'hover:bg-white/[0.05] hover:text-[var(--film-text-dim)]';
const isInspectorTab = (value: string): value is InspectorTab =>
value === 'badges' || value === 'selection';

const Inspector: React.FC<Props> = memo(({ config, setConfig }) => {
const { activeTab, setActiveTab, selectedIds, selectedLogo, selectedMinimalElements } = useEditor();
const Inspector: React.FC<Props> = memo(({ config, setConfig, hideTabBar = false, mode }) => {
const { activeTab, setActiveTab, selectedIds, selectedLogo, selectedMinimalElements } =
useEditor();
const selectedCount = selectedIds.size + (selectedLogo ? 1 : 0) + selectedMinimalElements.size;
const isMinimalPreset = (config.uiPreset ?? 'b') === 'm';
const hasBadges = config.ratings.length > 0;
Expand All @@ -32,18 +35,23 @@ const Inspector: React.FC<Props> = memo(({ config, setConfig }) => {
? 'Logo'
: 'Badges';

const tabs: { id: InspectorTab; label: string; Icon: React.ElementType; visible: boolean }[] = [
{ id: 'badges', label: primaryTabLabel, Icon: Badge, visible: hasBadges || hasLogo || isMinimalPreset },
const tabs = [
{
id: 'selection',
id: 'badges' as const,
label: primaryTabLabel,
icon: <Badge size={11} strokeWidth={2} />,
visible: hasBadges || hasLogo || isMinimalPreset,
},
{
id: 'selection' as const,
label: selectedCount > 0 ? `${selectedCount} selected` : 'Selection',
Icon: MousePointer2,
icon: <MousePointer2 size={11} strokeWidth={2} />,
visible: true,
},
];

const visibleTabs = tabs.filter((tab) => tab.visible);
const activeInspectorTab = isInspectorTab(activeTab) ? activeTab : undefined;
const activeInspectorTab = mode ?? (isInspectorTab(activeTab) ? activeTab : undefined);
const currentTab = visibleTabs.some((tab) => tab.id === activeInspectorTab)
? activeInspectorTab
: visibleTabs[0]?.id;
Expand All @@ -52,44 +60,34 @@ const Inspector: React.FC<Props> = memo(({ config, setConfig }) => {

return (
<SidebarLayout
side="right"
header={
<div
className="flex rounded-lg p-0.5 gap-0.5"
style={{
background: 'var(--film-char)',
border: '1px solid rgba(255,255,255,0.05)',
}}
>
{visibleTabs.map(({ id, label, Icon }) => (
<button
key={id}
onClick={() => setActiveTab(id)}
aria-pressed={currentTab === id}
className={clsx(
'flex-1 flex items-center justify-center gap-1.5 py-1.5 rounded-md text-[11px] font-medium transition-all duration-150 outline-none select-none syne-font',
currentTab !== id && INACTIVE_TAB_HOVER_CLASSES
)}
style={{
background: currentTab === id ? 'var(--film-mid)' : 'transparent',
color: currentTab === id ? 'var(--film-cream)' : 'var(--film-text-dim)',
boxShadow: currentTab === id ? '0 1px 4px rgba(0,0,0,0.3)' : 'none',
}}
>
<Icon size={11} strokeWidth={2} />
{label}
</button>
))}
</div>
hideTabBar ? undefined : (
<PanelSwitcher
value={currentTab}
onChange={(tab) => setActiveTab(tab)}
items={visibleTabs}
/>
)
}
>
<PropertyPanel
config={config}
setConfig={setConfig}
selectedIds={selectedIds}
selectedLogo={selectedLogo}
selectedMinimalElements={selectedMinimalElements}
mode={currentTab}
/>
{currentTab === 'badges' ? (
<BadgesPanel
config={config}
setConfig={setConfig}
selectedIds={selectedIds}
selectedLogo={selectedLogo}
selectedMinimalElements={selectedMinimalElements}
/>
) : (
<SelectionPanel
config={config}
setConfig={setConfig}
selectedIds={selectedIds}
selectedLogo={selectedLogo}
selectedMinimalElements={selectedMinimalElements}
/>
)}
</SidebarLayout>
);
});
Expand Down
45 changes: 45 additions & 0 deletions src/components/builder/components/navigation/BuilderModeToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { PanelsTopLeft } from 'lucide-react';
import type { BuilderMode } from '../../types';

interface Props {
mode: BuilderMode;
onChange: (mode: BuilderMode) => void;
}

const OPTIONS: { id: BuilderMode; label: string }[] = [
{ id: 'simple', label: 'Simple' },
{ id: 'advanced', label: 'Advanced' },
];

const BuilderModeToggle: React.FC<Props> = ({ mode, onChange }) => (
<div
className="flex items-center gap-1 h-8 p-0.5 rounded-lg border border-[rgba(196,124,46,0.18)] bg-[rgba(196,124,46,0.08)]"
aria-label="Builder mode"
>
<PanelsTopLeft size={13} className="ml-1 text-[var(--film-amber)] hidden sm:block" />
{OPTIONS.map((option) => {
const active = mode === option.id;
return (
<button
key={option.id}
type="button"
onClick={() => onChange(option.id)}
aria-pressed={active}
className="h-6 px-2 rounded-md text-[10px] syne-font font-bold uppercase tracking-wider transition-all active:scale-95"
style={{
background: active ? 'var(--film-amber)' : 'transparent',
color: active ? '#070706' : 'var(--film-text-dim)',
}}
>
<span className={option.id === 'advanced' ? 'hidden min-[901px]:inline' : ''}>
{option.label}
</span>
{option.id === 'advanced' && <span className="min-[901px]:hidden">Adv</span>}
</button>
);
})}
</div>
);

export default BuilderModeToggle;
56 changes: 56 additions & 0 deletions src/components/builder/components/navigation/PanelSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { memo } from 'react';
import clsx from 'clsx';

export interface PanelSwitcherItem<T extends string> {
id: T;
label: string;
icon?: React.ReactNode;
visible?: boolean;
}

interface Props<T extends string> {
items: PanelSwitcherItem<T>[];
value: T;
onChange: (value: T) => void;
className?: string;
}

const PanelSwitcher = <T extends string>({ items, value, onChange, className }: Props<T>) => {
const visibleItems = items.filter((item) => item.visible !== false);

return (
<div
className={clsx('flex rounded-lg p-0.5 gap-0.5', className)}
style={{
background: 'var(--film-char)',
border: '1px solid rgba(255,255,255,0.05)',
}}
>
{visibleItems.map((item) => {
const active = value === item.id;
return (
<button
key={item.id}
type="button"
onClick={() => onChange(item.id)}
aria-pressed={active}
className={clsx(
'flex-1 flex items-center justify-center gap-1.5 py-1.5 rounded-md text-[11px] font-medium transition-all duration-150 outline-none select-none syne-font',
!active && 'hover:bg-[rgba(196,124,46,0.08)] hover:text-[var(--film-text-label)]'
)}
style={{
background: active ? 'var(--film-mid)' : 'transparent',
color: active ? 'var(--film-cream)' : 'var(--film-text-dim)',
boxShadow: active ? '0 1px 4px rgba(0,0,0,0.3)' : 'none',
}}
>
{item.icon}
{item.label}
</button>
);
})}
</div>
);
};

export default memo(PanelSwitcher) as typeof PanelSwitcher;
2 changes: 1 addition & 1 deletion src/components/builder/context/EditorContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import React, { createContext, useContext, useState, useCallback } from 'react';
import type { RatingType } from '../types';

type TabType = 'source' | 'layers' | 'poster' | 'badges' | 'logo' | 'selection';
export type TabType = 'source' | 'layers' | 'poster' | 'badges' | 'logo' | 'selection';
export type SheetMode = 'hidden' | 'half' | 'full';

interface ViewOptions {
Expand Down
Loading