@morphika/andami 0.5.1 → 0.5.3
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/README.md +27 -2
- package/app/admin/assets/page.tsx +6 -6
- package/app/admin/database/page.tsx +302 -302
- package/app/admin/error.tsx +53 -53
- package/app/admin/layout.tsx +332 -320
- package/app/admin/navigation/page.tsx +255 -255
- package/app/admin/pages/[slug]/page.tsx +44 -27
- package/app/admin/pages/page.tsx +24 -19
- package/app/admin/projects/page.tsx +30 -21
- package/app/admin/setup/page.tsx +1 -1
- package/app/admin/styles/page.tsx +1 -1
- package/app/api/admin/assets/register/route.ts +51 -14
- package/app/api/admin/assets/registry/route.ts +4 -1
- package/app/api/admin/assets/relink/confirm/route.ts +4 -1
- package/app/api/admin/assets/relink/route.ts +4 -1
- package/app/api/admin/assets/scan/route.ts +4 -1
- package/app/api/admin/backups/restore-data/route.ts +4 -1
- package/app/api/admin/r2/connect/route.ts +4 -1
- package/app/api/admin/r2/delete/route.ts +4 -1
- package/app/api/admin/r2/rename/route.ts +4 -1
- package/app/api/admin/r2/upload-url/route.ts +4 -1
- package/app/api/admin/revalidate/route.ts +4 -1
- package/app/api/admin/storage/switch/route.ts +4 -1
- package/app/api/custom-sections/[id]/route.ts +5 -6
- package/components/admin/MetadataEditor.tsx +6 -6
- package/components/admin/PublishToggle.tsx +2 -2
- package/components/admin/nav-builder/NavBuilder.tsx +1 -1
- package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
- package/components/admin/nav-builder/NavGridCell.tsx +48 -48
- package/components/admin/nav-builder/NavGridItem.tsx +8 -6
- package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
- package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
- package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
- package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
- package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
- package/components/admin/nav-builder/NavSettingsFields.tsx +518 -514
- package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
- package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
- package/components/admin/setup-wizard/DoneStep.tsx +1 -1
- package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
- package/components/admin/setup-wizard/StorageStep.tsx +2 -2
- package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
- package/components/admin/styles/ColorsEditor.tsx +9 -8
- package/components/admin/styles/FontsEditor.tsx +9 -7
- package/components/admin/styles/GridLayoutEditor.tsx +9 -9
- package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
- package/components/admin/styles/TypographyEditor.tsx +6 -6
- package/components/admin/styles/shared.tsx +68 -68
- package/components/blocks/AudioBlockRenderer.tsx +286 -286
- package/components/blocks/CoverSectionRenderer.tsx +7 -1
- package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
- package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
- package/components/blocks/SectionV2Renderer.tsx +8 -1
- package/components/builder/BlockCardIcons.tsx +316 -316
- package/components/builder/BlockTypePicker.tsx +1 -1
- package/components/builder/BubbleIcons.tsx +104 -0
- package/components/builder/BuilderCanvas.tsx +2 -0
- package/components/builder/CanvasMinimap.tsx +66 -49
- package/components/builder/CanvasToolbar.tsx +31 -41
- package/components/builder/CoverSectionCanvas.tsx +363 -363
- package/components/builder/DeviceFrame.tsx +1 -1
- package/components/builder/DndWrapper.tsx +3 -3
- package/components/builder/InsertionLines.tsx +1 -1
- package/components/builder/SectionCardIcons.tsx +421 -320
- package/components/builder/SectionEditorBar.tsx +5 -3
- package/components/builder/SectionTypePicker.tsx +7 -5
- package/components/builder/SectionV2Canvas.tsx +1 -1
- package/components/builder/SectionV2Column.tsx +82 -68
- package/components/builder/SettingsPanel.tsx +21 -17
- package/components/builder/SortableBlock.tsx +93 -73
- package/components/builder/SortableRow.tsx +33 -35
- package/components/builder/VirtualAssetGrid.tsx +10 -4
- package/components/builder/asset-browser/R2BrowserContent.tsx +18 -14
- package/components/builder/blockStyles.tsx +192 -185
- package/components/builder/color-picker/AlphaSlider.tsx +141 -141
- package/components/builder/color-picker/ColorInputs.tsx +105 -105
- package/components/builder/color-picker/EyedropperButton.tsx +75 -74
- package/components/builder/color-picker/HueSlider.tsx +124 -124
- package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
- package/components/builder/color-picker/SwatchBar.tsx +98 -93
- package/components/builder/color-picker/UnifiedColorPicker.tsx +11 -6
- package/components/builder/editors/AudioBlockEditor.tsx +242 -242
- package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -360
- package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
- package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
- package/components/builder/editors/HoverEffectPicker.tsx +2 -2
- package/components/builder/editors/ImageBlockEditor.tsx +2 -2
- package/components/builder/editors/ImageGridBlockEditor.tsx +8 -6
- package/components/builder/editors/MarqueeBlockEditor.tsx +622 -0
- package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
- package/components/builder/editors/ProjectGridEditor.tsx +21 -16
- package/components/builder/editors/SpacerBlockEditor.tsx +29 -27
- package/components/builder/editors/StaggerSettings.tsx +109 -109
- package/components/builder/editors/TextBlockEditor.tsx +22 -17
- package/components/builder/editors/TextStylePicker.tsx +1 -1
- package/components/builder/editors/VideoBlockEditor.tsx +2 -2
- package/components/builder/editors/index.ts +11 -10
- package/components/builder/editors/shared.tsx +10 -8
- package/components/builder/live-preview/LiveAudioPreview.tsx +120 -120
- package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +1 -1
- package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
- package/components/builder/live-preview/LiveImagePreview.tsx +4 -2
- package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
- package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
- package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
- package/components/builder/live-preview/ProjectCardWrapper.tsx +293 -291
- package/components/builder/live-preview/RichTextBubbleMenu.tsx +10 -6
- package/components/builder/live-preview/shared.tsx +5 -2
- package/components/builder/settings-panel/AnimationTab.tsx +138 -138
- package/components/builder/settings-panel/BlockLayoutTab.tsx +11 -9
- package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
- package/components/builder/settings-panel/ColumnV2LayoutTab.tsx +242 -0
- package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
- package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
- package/components/builder/settings-panel/CoverSectionSettings.tsx +337 -335
- package/components/builder/settings-panel/PageSettings.tsx +3 -3
- package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
- package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
- package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
- package/components/builder/settings-panel/SectionV2Settings.tsx +25 -20
- package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
- package/components/builder/settings-panel/index.ts +1 -0
- package/lib/animation/enter-types.ts +1 -0
- package/lib/animation/hover-effect-presets.ts +210 -210
- package/lib/animation/hover-effect-types.ts +1 -0
- package/lib/builder/block-registrations.ts +468 -417
- package/lib/builder/constants.ts +111 -111
- package/lib/builder/serializer/normalizers.ts +14 -0
- package/lib/builder/serializer/serializers.ts +27 -0
- package/lib/builder/store-sections.ts +23 -2
- package/lib/builder/types-slices.ts +428 -414
- package/lib/builder/types.ts +4 -1
- package/lib/config/index.ts +27 -27
- package/lib/sanity/queries.ts +48 -0
- package/lib/sanity/types.ts +112 -1
- package/lib/version.ts +1 -1
- package/package.json +7 -5
- package/sanity/schemas/blocks/audioBlock.ts +69 -69
- package/sanity/schemas/blocks/index.ts +12 -11
- package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
- package/sanity/schemas/index.ts +120 -117
- package/sanity/schemas/objects/coverSection.ts +32 -0
- package/sanity/schemas/objects/parallaxSlide.ts +32 -0
- package/sanity/schemas/pageSectionV2.ts +32 -0
- package/styles/admin.css +85 -85
- package/styles/animations.css +237 -237
- package/styles/base.css +114 -114
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
ViewportBadge,
|
|
25
25
|
StyledCheckbox,
|
|
26
26
|
} from "./shared";
|
|
27
|
+
import { BubbleTooltip } from "../BubbleIcons";
|
|
27
28
|
|
|
28
29
|
// ============================================
|
|
29
30
|
// Constants
|
|
@@ -72,7 +73,7 @@ function SegmentedControl<T extends string>({
|
|
|
72
73
|
onClick={() => onChange(opt.value)}
|
|
73
74
|
className={`flex-1 px-2 py-1.5 text-xs rounded transition-colors ${
|
|
74
75
|
active
|
|
75
|
-
? "bg-[#
|
|
76
|
+
? "bg-[#3580f9] text-white"
|
|
76
77
|
: "bg-neutral-100 text-neutral-600 hover:bg-neutral-200"
|
|
77
78
|
}`}
|
|
78
79
|
>
|
|
@@ -112,7 +113,7 @@ function RangeSlider({
|
|
|
112
113
|
step={step}
|
|
113
114
|
value={value}
|
|
114
115
|
onChange={(e) => onChange(Number(e.target.value))}
|
|
115
|
-
className="flex-1 h-1 accent-[#
|
|
116
|
+
className="flex-1 h-1 accent-[#3580f9] cursor-pointer"
|
|
116
117
|
/>
|
|
117
118
|
<span className="text-[11px] text-neutral-500 w-8 text-right tabular-nums shrink-0">
|
|
118
119
|
{value}{suffix}
|
|
@@ -154,7 +155,7 @@ function RatioChips({
|
|
|
154
155
|
onClick={() => toggle(opt.value)}
|
|
155
156
|
className={`flex-1 flex flex-col items-center gap-1 px-1.5 py-2 rounded-lg text-[10px] transition-colors ${
|
|
156
157
|
active
|
|
157
|
-
? "bg-[#
|
|
158
|
+
? "bg-[#3580f9] text-white"
|
|
158
159
|
: "bg-neutral-100 text-neutral-500 hover:bg-neutral-200"
|
|
159
160
|
}`}
|
|
160
161
|
>
|
|
@@ -206,7 +207,7 @@ function CardRatioChips({
|
|
|
206
207
|
onClick={() => onChange(opt.value as "16/9" | "1/1" | "9/16" | null)}
|
|
207
208
|
className={`flex flex-col items-center gap-1 px-1.5 py-2 rounded-lg text-[10px] transition-colors ${
|
|
208
209
|
active
|
|
209
|
-
? "bg-[#
|
|
210
|
+
? "bg-[#3580f9] text-white"
|
|
210
211
|
: "bg-neutral-100 text-neutral-500 hover:bg-neutral-200"
|
|
211
212
|
}`}
|
|
212
213
|
>
|
|
@@ -511,12 +512,12 @@ export default function ProjectGridEditor({ block }: ProjectGridEditorProps) {
|
|
|
511
512
|
<React.Fragment key={item._key}>
|
|
512
513
|
<div
|
|
513
514
|
className={`flex items-center gap-2 px-2 py-1.5 group cursor-pointer transition-colors ${
|
|
514
|
-
isCardSelected ? "bg-[#
|
|
515
|
+
isCardSelected ? "bg-[#3580f9]/10 ring-1 ring-[#3580f9]/30 rounded-t-lg" : "bg-[#f5f5f5] hover:bg-[#efefef] rounded-lg"
|
|
515
516
|
}`}
|
|
516
517
|
onClick={() => selectProjectCard(isCardSelected ? null : item._key)}
|
|
517
518
|
>
|
|
518
519
|
{/* Drag grip */}
|
|
519
|
-
<span className="text-neutral-300 shrink-0 cursor-grab"
|
|
520
|
+
<span className="group/bb relative text-neutral-300 shrink-0 cursor-grab" aria-label="Reorder">
|
|
520
521
|
<svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor">
|
|
521
522
|
<circle cx="3" cy="2" r="1" />
|
|
522
523
|
<circle cx="7" cy="2" r="1" />
|
|
@@ -525,6 +526,7 @@ export default function ProjectGridEditor({ block }: ProjectGridEditorProps) {
|
|
|
525
526
|
<circle cx="3" cy="8" r="1" />
|
|
526
527
|
<circle cx="7" cy="8" r="1" />
|
|
527
528
|
</svg>
|
|
529
|
+
<BubbleTooltip>Reorder</BubbleTooltip>
|
|
528
530
|
</span>
|
|
529
531
|
|
|
530
532
|
{/* Project name */}
|
|
@@ -543,43 +545,46 @@ export default function ProjectGridEditor({ block }: ProjectGridEditorProps) {
|
|
|
543
545
|
<button
|
|
544
546
|
onClick={(e) => { e.stopPropagation(); moveProject(i, -1); }}
|
|
545
547
|
disabled={i === 0}
|
|
546
|
-
className="p-0.5 text-neutral-400 hover:text-neutral-700 disabled:opacity-20 transition-colors"
|
|
547
|
-
|
|
548
|
+
className="group/bb relative p-0.5 text-neutral-400 hover:text-neutral-700 disabled:opacity-20 transition-colors"
|
|
549
|
+
aria-label="Move up"
|
|
548
550
|
>
|
|
549
551
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
550
552
|
<polyline points="18 15 12 9 6 15" />
|
|
551
553
|
</svg>
|
|
554
|
+
<BubbleTooltip>Move up</BubbleTooltip>
|
|
552
555
|
</button>
|
|
553
556
|
<button
|
|
554
557
|
onClick={(e) => { e.stopPropagation(); moveProject(i, 1); }}
|
|
555
558
|
disabled={i === (block.projects || []).length - 1}
|
|
556
|
-
className="p-0.5 text-neutral-400 hover:text-neutral-700 disabled:opacity-20 transition-colors"
|
|
557
|
-
|
|
559
|
+
className="group/bb relative p-0.5 text-neutral-400 hover:text-neutral-700 disabled:opacity-20 transition-colors"
|
|
560
|
+
aria-label="Move down"
|
|
558
561
|
>
|
|
559
562
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
560
563
|
<polyline points="6 9 12 15 18 9" />
|
|
561
564
|
</svg>
|
|
565
|
+
<BubbleTooltip>Move down</BubbleTooltip>
|
|
562
566
|
</button>
|
|
563
567
|
|
|
564
568
|
{/* Remove */}
|
|
565
569
|
<button
|
|
566
570
|
onClick={(e) => { e.stopPropagation(); removeProject(item._key); }}
|
|
567
|
-
className="p-0.5 text-neutral-400 hover:text-red-500 transition-colors"
|
|
568
|
-
|
|
571
|
+
className="group/bb relative p-0.5 text-neutral-400 hover:text-red-500 transition-colors"
|
|
572
|
+
aria-label="Remove"
|
|
569
573
|
>
|
|
570
574
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
571
575
|
<line x1="18" y1="6" x2="6" y2="18" />
|
|
572
576
|
<line x1="6" y1="6" x2="18" y2="18" />
|
|
573
577
|
</svg>
|
|
578
|
+
<BubbleTooltip>Remove</BubbleTooltip>
|
|
574
579
|
</button>
|
|
575
580
|
</div>
|
|
576
581
|
|
|
577
582
|
{/* Per-card settings — expanded below this project row */}
|
|
578
583
|
{isCardSelected && (
|
|
579
|
-
<div className="px-2 pb-2 pt-1 -mt-1 rounded-b-lg bg-[#
|
|
584
|
+
<div className="px-2 pb-2 pt-1 -mt-1 rounded-b-lg bg-[#3580f9]/5 border-x border-b border-[#3580f9]/15">
|
|
580
585
|
{isResponsive && (
|
|
581
|
-
<div className="flex items-center gap-1.5 px-2 py-1 mb-1 rounded bg-[#
|
|
582
|
-
<span className="text-[10px] font-medium text-[#
|
|
586
|
+
<div className="flex items-center gap-1.5 px-2 py-1 mb-1 rounded bg-[#3580f9]/8">
|
|
587
|
+
<span className="text-[10px] font-medium text-[#3580f9]">
|
|
583
588
|
{activeViewport === "tablet" ? "Tablet" : "Phone"} override
|
|
584
589
|
</span>
|
|
585
590
|
</div>
|
|
@@ -614,7 +619,7 @@ export default function ProjectGridEditor({ block }: ProjectGridEditorProps) {
|
|
|
614
619
|
placeholder="Search projects..."
|
|
615
620
|
value={search}
|
|
616
621
|
onChange={(e) => setSearch(e.target.value)}
|
|
617
|
-
className="w-full rounded-md bg-[#f5f5f5] px-2.5 py-1.5 text-xs text-neutral-900 placeholder:text-neutral-400 outline-none focus:bg-white focus:ring-1 focus:ring-[#
|
|
622
|
+
className="w-full rounded-md bg-[#f5f5f5] px-2.5 py-1.5 text-xs text-neutral-900 placeholder:text-neutral-400 outline-none focus:bg-white focus:ring-1 focus:ring-[#3580f9]/20"
|
|
618
623
|
autoFocus
|
|
619
624
|
/>
|
|
620
625
|
</div>
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
useActiveViewport,
|
|
15
15
|
INPUT_CLASS,
|
|
16
16
|
} from "./shared";
|
|
17
|
+
import { BubbleTooltip } from "../BubbleIcons";
|
|
17
18
|
|
|
18
19
|
interface Props {
|
|
19
20
|
block: SpacerBlock;
|
|
@@ -79,29 +80,30 @@ export default function SpacerBlockEditor({ block }: Props) {
|
|
|
79
80
|
onReset={() => resetOverride("height")}
|
|
80
81
|
>
|
|
81
82
|
<div className="flex gap-1">
|
|
82
|
-
{HEIGHT_PRESETS.map((preset) =>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
83
|
+
{HEIGHT_PRESETS.map((preset) => {
|
|
84
|
+
const tooltip =
|
|
85
|
+
preset.value === "custom" ? "Custom height" : `${preset.px}px`;
|
|
86
|
+
return (
|
|
87
|
+
<button
|
|
88
|
+
key={preset.value}
|
|
89
|
+
onClick={() => updateResponsive("height", preset.value)}
|
|
90
|
+
className={`group/bb relative flex-1 rounded border py-1.5 text-xs transition-colors flex flex-col items-center gap-0.5 ${
|
|
91
|
+
effectiveHeight === preset.value
|
|
92
|
+
? "border-[#3580f9] bg-[#3580f9]/20 text-neutral-900"
|
|
93
|
+
: "border-neutral-200 bg-white text-neutral-500 hover:border-neutral-600"
|
|
94
|
+
}`}
|
|
95
|
+
aria-label={tooltip}
|
|
96
|
+
>
|
|
97
|
+
<span>{preset.label}</span>
|
|
98
|
+
{preset.value !== "custom" && (
|
|
99
|
+
<span className="text-[8px] text-neutral-600">
|
|
100
|
+
{preset.px}px
|
|
101
|
+
</span>
|
|
102
|
+
)}
|
|
103
|
+
<BubbleTooltip>{tooltip}</BubbleTooltip>
|
|
104
|
+
</button>
|
|
105
|
+
);
|
|
106
|
+
})}
|
|
105
107
|
</div>
|
|
106
108
|
</ResponsiveField>
|
|
107
109
|
|
|
@@ -122,7 +124,7 @@ export default function SpacerBlockEditor({ block }: Props) {
|
|
|
122
124
|
onChange={(e) =>
|
|
123
125
|
updateResponsive("custom_height", parseInt(e.target.value))
|
|
124
126
|
}
|
|
125
|
-
className="flex-1 accent-[#
|
|
127
|
+
className="flex-1 accent-[#3580f9]"
|
|
126
128
|
/>
|
|
127
129
|
<input
|
|
128
130
|
type="number"
|
|
@@ -146,9 +148,9 @@ export default function SpacerBlockEditor({ block }: Props) {
|
|
|
146
148
|
style={{ height: `${Math.min(currentPx, 200)}px` }}
|
|
147
149
|
>
|
|
148
150
|
<div className="absolute left-0 right-0 top-1/2 border-t border-dashed border-neutral-200" />
|
|
149
|
-
<div className="absolute left-2 top-0 w-px h-2 bg-[#
|
|
150
|
-
<div className="absolute left-2 bottom-0 w-px h-2 bg-[#
|
|
151
|
-
<div className="absolute left-2 top-0 bottom-0 w-px bg-[#
|
|
151
|
+
<div className="absolute left-2 top-0 w-px h-2 bg-[#3580f9]" />
|
|
152
|
+
<div className="absolute left-2 bottom-0 w-px h-2 bg-[#3580f9]" />
|
|
153
|
+
<div className="absolute left-2 top-0 bottom-0 w-px bg-[#3580f9]/20" />
|
|
152
154
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
153
155
|
<span className="text-xs text-neutral-500 bg-white/80 px-1.5 py-0.5 rounded">
|
|
154
156
|
{currentPx}px
|
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
import { StaggerIcon } from "./section-icons";
|
|
2
|
-
import { SettingsSection, SettingsField } from "./shared";
|
|
3
|
-
|
|
4
|
-
// ============================================
|
|
5
|
-
// Types
|
|
6
|
-
// ============================================
|
|
7
|
-
|
|
8
|
-
export interface StaggerConfig {
|
|
9
|
-
enabled?: boolean;
|
|
10
|
-
delayPerChild?: number;
|
|
11
|
-
direction?: "left-to-right" | "right-to-left";
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// ============================================
|
|
15
|
-
// CSS constants
|
|
16
|
-
// ============================================
|
|
17
|
-
|
|
18
|
-
const SLIDER_CLASS =
|
|
19
|
-
"w-full h-1.5 rounded-full bg-[#e5e5e5] appearance-none cursor-pointer accent-[#
|
|
20
|
-
|
|
21
|
-
const TOGGLE_CLASS_ON =
|
|
22
|
-
"relative w-8 h-[18px] rounded-full bg-[#
|
|
23
|
-
|
|
24
|
-
const TOGGLE_CLASS_OFF =
|
|
25
|
-
"relative w-8 h-[18px] rounded-full bg-[#d4d4d4] transition-colors cursor-pointer after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:w-[14px] after:h-[14px] after:rounded-full after:bg-white after:shadow-sm after:transition-all";
|
|
26
|
-
|
|
27
|
-
// ============================================
|
|
28
|
-
// Stagger Section (row mode only)
|
|
29
|
-
// ============================================
|
|
30
|
-
|
|
31
|
-
export default function StaggerSettings({
|
|
32
|
-
stagger,
|
|
33
|
-
onChange,
|
|
34
|
-
}: {
|
|
35
|
-
stagger?: StaggerConfig;
|
|
36
|
-
onChange: (s: StaggerConfig) => void;
|
|
37
|
-
}) {
|
|
38
|
-
const enabled = stagger?.enabled ?? false;
|
|
39
|
-
const delay = stagger?.delayPerChild ?? 100;
|
|
40
|
-
const direction = stagger?.direction ?? "left-to-right";
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<SettingsSection title="Stagger Children" icon={<StaggerIcon />}>
|
|
44
|
-
<div className="space-y-3">
|
|
45
|
-
{/* Enable toggle */}
|
|
46
|
-
<div className="flex items-center justify-between">
|
|
47
|
-
<span className="text-[11px] text-neutral-500">
|
|
48
|
-
{enabled ? "Enabled" : "Disabled"}
|
|
49
|
-
</span>
|
|
50
|
-
<button
|
|
51
|
-
type="button"
|
|
52
|
-
onClick={() => onChange({ ...stagger, enabled: !enabled })}
|
|
53
|
-
className={enabled ? TOGGLE_CLASS_ON : TOGGLE_CLASS_OFF}
|
|
54
|
-
aria-label={enabled ? "Stagger enabled" : "Stagger disabled"}
|
|
55
|
-
/>
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
{enabled && (
|
|
59
|
-
<>
|
|
60
|
-
{/* Delay slider */}
|
|
61
|
-
<SettingsField label={`Delay — ${delay}ms`}>
|
|
62
|
-
<input
|
|
63
|
-
type="range"
|
|
64
|
-
min={50}
|
|
65
|
-
max={5000}
|
|
66
|
-
step={10}
|
|
67
|
-
value={delay}
|
|
68
|
-
onChange={(e) =>
|
|
69
|
-
onChange({ ...stagger, enabled: true, delayPerChild: Number(e.target.value) })
|
|
70
|
-
}
|
|
71
|
-
className={SLIDER_CLASS}
|
|
72
|
-
/>
|
|
73
|
-
<div className="flex justify-between mt-0.5">
|
|
74
|
-
<span className="text-[10px] text-neutral-400">50ms</span>
|
|
75
|
-
<span className="text-[10px] text-neutral-400">5000ms</span>
|
|
76
|
-
</div>
|
|
77
|
-
</SettingsField>
|
|
78
|
-
|
|
79
|
-
{/* Direction */}
|
|
80
|
-
<SettingsField label="Direction">
|
|
81
|
-
<div className="flex gap-1">
|
|
82
|
-
<button
|
|
83
|
-
onClick={() => onChange({ ...stagger, enabled: true, direction: "left-to-right" })}
|
|
84
|
-
className={`flex-1 py-1.5 rounded-md text-[10px] font-medium transition-all ${
|
|
85
|
-
direction === "left-to-right"
|
|
86
|
-
? "bg-[#
|
|
87
|
-
: "bg-[#f5f5f5] text-neutral-500 hover:bg-[#ebebeb]"
|
|
88
|
-
}`}
|
|
89
|
-
>
|
|
90
|
-
L → R
|
|
91
|
-
</button>
|
|
92
|
-
<button
|
|
93
|
-
onClick={() => onChange({ ...stagger, enabled: true, direction: "right-to-left" })}
|
|
94
|
-
className={`flex-1 py-1.5 rounded-md text-[10px] font-medium transition-all ${
|
|
95
|
-
direction === "right-to-left"
|
|
96
|
-
? "bg-[#
|
|
97
|
-
: "bg-[#f5f5f5] text-neutral-500 hover:bg-[#ebebeb]"
|
|
98
|
-
}`}
|
|
99
|
-
>
|
|
100
|
-
R → L
|
|
101
|
-
</button>
|
|
102
|
-
</div>
|
|
103
|
-
</SettingsField>
|
|
104
|
-
</>
|
|
105
|
-
)}
|
|
106
|
-
</div>
|
|
107
|
-
</SettingsSection>
|
|
108
|
-
);
|
|
109
|
-
}
|
|
1
|
+
import { StaggerIcon } from "./section-icons";
|
|
2
|
+
import { SettingsSection, SettingsField } from "./shared";
|
|
3
|
+
|
|
4
|
+
// ============================================
|
|
5
|
+
// Types
|
|
6
|
+
// ============================================
|
|
7
|
+
|
|
8
|
+
export interface StaggerConfig {
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
delayPerChild?: number;
|
|
11
|
+
direction?: "left-to-right" | "right-to-left";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ============================================
|
|
15
|
+
// CSS constants
|
|
16
|
+
// ============================================
|
|
17
|
+
|
|
18
|
+
const SLIDER_CLASS =
|
|
19
|
+
"w-full h-1.5 rounded-full bg-[#e5e5e5] appearance-none cursor-pointer accent-[#3580f9] [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3.5 [&::-webkit-slider-thumb]:h-3.5 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[#3580f9] [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-white [&::-webkit-slider-thumb]:shadow-sm";
|
|
20
|
+
|
|
21
|
+
const TOGGLE_CLASS_ON =
|
|
22
|
+
"relative w-8 h-[18px] rounded-full bg-[#3580f9] transition-colors cursor-pointer after:content-[''] after:absolute after:top-[2px] after:left-[15px] after:w-[14px] after:h-[14px] after:rounded-full after:bg-white after:shadow-sm after:transition-all";
|
|
23
|
+
|
|
24
|
+
const TOGGLE_CLASS_OFF =
|
|
25
|
+
"relative w-8 h-[18px] rounded-full bg-[#d4d4d4] transition-colors cursor-pointer after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:w-[14px] after:h-[14px] after:rounded-full after:bg-white after:shadow-sm after:transition-all";
|
|
26
|
+
|
|
27
|
+
// ============================================
|
|
28
|
+
// Stagger Section (row mode only)
|
|
29
|
+
// ============================================
|
|
30
|
+
|
|
31
|
+
export default function StaggerSettings({
|
|
32
|
+
stagger,
|
|
33
|
+
onChange,
|
|
34
|
+
}: {
|
|
35
|
+
stagger?: StaggerConfig;
|
|
36
|
+
onChange: (s: StaggerConfig) => void;
|
|
37
|
+
}) {
|
|
38
|
+
const enabled = stagger?.enabled ?? false;
|
|
39
|
+
const delay = stagger?.delayPerChild ?? 100;
|
|
40
|
+
const direction = stagger?.direction ?? "left-to-right";
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<SettingsSection title="Stagger Children" icon={<StaggerIcon />}>
|
|
44
|
+
<div className="space-y-3">
|
|
45
|
+
{/* Enable toggle */}
|
|
46
|
+
<div className="flex items-center justify-between">
|
|
47
|
+
<span className="text-[11px] text-neutral-500">
|
|
48
|
+
{enabled ? "Enabled" : "Disabled"}
|
|
49
|
+
</span>
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
onClick={() => onChange({ ...stagger, enabled: !enabled })}
|
|
53
|
+
className={enabled ? TOGGLE_CLASS_ON : TOGGLE_CLASS_OFF}
|
|
54
|
+
aria-label={enabled ? "Stagger enabled" : "Stagger disabled"}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
{enabled && (
|
|
59
|
+
<>
|
|
60
|
+
{/* Delay slider */}
|
|
61
|
+
<SettingsField label={`Delay — ${delay}ms`}>
|
|
62
|
+
<input
|
|
63
|
+
type="range"
|
|
64
|
+
min={50}
|
|
65
|
+
max={5000}
|
|
66
|
+
step={10}
|
|
67
|
+
value={delay}
|
|
68
|
+
onChange={(e) =>
|
|
69
|
+
onChange({ ...stagger, enabled: true, delayPerChild: Number(e.target.value) })
|
|
70
|
+
}
|
|
71
|
+
className={SLIDER_CLASS}
|
|
72
|
+
/>
|
|
73
|
+
<div className="flex justify-between mt-0.5">
|
|
74
|
+
<span className="text-[10px] text-neutral-400">50ms</span>
|
|
75
|
+
<span className="text-[10px] text-neutral-400">5000ms</span>
|
|
76
|
+
</div>
|
|
77
|
+
</SettingsField>
|
|
78
|
+
|
|
79
|
+
{/* Direction */}
|
|
80
|
+
<SettingsField label="Direction">
|
|
81
|
+
<div className="flex gap-1">
|
|
82
|
+
<button
|
|
83
|
+
onClick={() => onChange({ ...stagger, enabled: true, direction: "left-to-right" })}
|
|
84
|
+
className={`flex-1 py-1.5 rounded-md text-[10px] font-medium transition-all ${
|
|
85
|
+
direction === "left-to-right"
|
|
86
|
+
? "bg-[#3580f9] text-white"
|
|
87
|
+
: "bg-[#f5f5f5] text-neutral-500 hover:bg-[#ebebeb]"
|
|
88
|
+
}`}
|
|
89
|
+
>
|
|
90
|
+
L → R
|
|
91
|
+
</button>
|
|
92
|
+
<button
|
|
93
|
+
onClick={() => onChange({ ...stagger, enabled: true, direction: "right-to-left" })}
|
|
94
|
+
className={`flex-1 py-1.5 rounded-md text-[10px] font-medium transition-all ${
|
|
95
|
+
direction === "right-to-left"
|
|
96
|
+
? "bg-[#3580f9] text-white"
|
|
97
|
+
: "bg-[#f5f5f5] text-neutral-500 hover:bg-[#ebebeb]"
|
|
98
|
+
}`}
|
|
99
|
+
>
|
|
100
|
+
R → L
|
|
101
|
+
</button>
|
|
102
|
+
</div>
|
|
103
|
+
</SettingsField>
|
|
104
|
+
</>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
</SettingsSection>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
AlignRightIcon,
|
|
31
31
|
AlignJustifyIcon,
|
|
32
32
|
} from "./TextAlignmentIcons";
|
|
33
|
+
import { BubbleTooltip } from "../BubbleIcons";
|
|
33
34
|
|
|
34
35
|
// ============================================
|
|
35
36
|
// Responsive style field — MUST be defined outside the editor component
|
|
@@ -62,7 +63,7 @@ function ResponsiveStyleField({
|
|
|
62
63
|
<span className="block text-[9px] text-neutral-300 italic mt-0.5">inherited</span>
|
|
63
64
|
)}
|
|
64
65
|
{isOverridden && (
|
|
65
|
-
<span className="block text-[9px] text-[#
|
|
66
|
+
<span className="block text-[9px] text-[#3580f9] mt-0.5">overridden</span>
|
|
66
67
|
)}
|
|
67
68
|
</label>
|
|
68
69
|
<div className="flex-1 min-w-0">
|
|
@@ -292,25 +293,29 @@ export default function TextBlockEditor({ block }: { block: TextBlock }) {
|
|
|
292
293
|
|
|
293
294
|
<ResponsiveStyleField label="Align" subProp="alignment" viewport={viewport} isOverridden={viewport !== "desktop" && hasStyleOverride("alignment")} onReset={resetStyleOverride}>
|
|
294
295
|
<div className="flex gap-0.5 bg-[#f5f5f5] rounded-lg p-0.5">
|
|
295
|
-
{alignments.map(({ value, icon }) =>
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
296
|
+
{alignments.map(({ value, icon }) => {
|
|
297
|
+
const label = value.charAt(0).toUpperCase() + value.slice(1);
|
|
298
|
+
return (
|
|
299
|
+
<button
|
|
300
|
+
key={value}
|
|
301
|
+
onClick={() => updateStyleResponsive("alignment", value)}
|
|
302
|
+
className={`group/bb relative flex-1 flex items-center justify-center py-[5px] rounded-md transition-all ${
|
|
303
|
+
currentAlignment === value
|
|
304
|
+
? "bg-white text-neutral-900 shadow-[0_1px_3px_rgba(0,0,0,0.08)]"
|
|
305
|
+
: "text-neutral-300 hover:text-neutral-500"
|
|
306
|
+
}`}
|
|
307
|
+
aria-label={label}
|
|
308
|
+
>
|
|
309
|
+
{icon}
|
|
310
|
+
<BubbleTooltip>{label}</BubbleTooltip>
|
|
311
|
+
</button>
|
|
312
|
+
);
|
|
313
|
+
})}
|
|
309
314
|
</div>
|
|
310
315
|
</ResponsiveStyleField>
|
|
311
316
|
|
|
312
317
|
<ResponsiveStyleField label="Size" subProp="fontSize" viewport={viewport} isOverridden={viewport !== "desktop" && hasStyleOverride("fontSize")} onReset={resetStyleOverride}>
|
|
313
|
-
<div className="flex items-center gap-0 bg-[#f5f5f5] rounded-lg overflow-hidden transition-all border border-transparent focus-within:bg-white focus-within:border-[#
|
|
318
|
+
<div className="flex items-center gap-0 bg-[#f5f5f5] rounded-lg overflow-hidden transition-all border border-transparent focus-within:bg-white focus-within:border-[#3580f9] focus-within:shadow-[0_0_0_3px_rgba(53, 128, 249,0.06)]">
|
|
314
319
|
<input
|
|
315
320
|
type="number"
|
|
316
321
|
min={1}
|
|
@@ -450,7 +455,7 @@ export default function TextBlockEditor({ block }: { block: TextBlock }) {
|
|
|
450
455
|
onChange={(e) =>
|
|
451
456
|
updateStyleResponsive("opacity", parseInt(e.target.value) / 100)
|
|
452
457
|
}
|
|
453
|
-
className="flex-1 accent-[#
|
|
458
|
+
className="flex-1 accent-[#3580f9]"
|
|
454
459
|
/>
|
|
455
460
|
<span className="text-xs text-neutral-900 w-10 text-right tabular-nums">
|
|
456
461
|
{Math.round((getEffectiveStyleValue<number>("opacity", style.opacity ?? 1)) * 100)}%
|
|
@@ -134,7 +134,7 @@ export default function TextStylePicker({
|
|
|
134
134
|
onChange={(e) => setSearch(e.target.value)}
|
|
135
135
|
placeholder="Search..."
|
|
136
136
|
autoFocus
|
|
137
|
-
className="w-full rounded-md bg-[#f5f5f5] px-2 py-1.5 text-xs text-neutral-900 border-none outline-none focus:bg-white focus:ring-1 focus:ring-[#
|
|
137
|
+
className="w-full rounded-md bg-[#f5f5f5] px-2 py-1.5 text-xs text-neutral-900 border-none outline-none focus:bg-white focus:ring-1 focus:ring-[#3580f9]/30"
|
|
138
138
|
/>
|
|
139
139
|
</div>
|
|
140
140
|
<div className="max-h-[220px] overflow-y-auto py-1">
|
|
@@ -111,7 +111,7 @@ export default function VideoBlockEditor({ block }: Props) {
|
|
|
111
111
|
onClick={() => update({ video_type: opt.value })}
|
|
112
112
|
className={`flex-1 rounded border py-1 text-xs transition-colors ${
|
|
113
113
|
(block.video_type || "vimeo") === opt.value
|
|
114
|
-
? "border-[#
|
|
114
|
+
? "border-[#3580f9] bg-[#3580f9]/20 text-neutral-900"
|
|
115
115
|
: "border-neutral-200 bg-white text-neutral-500 hover:border-neutral-600"
|
|
116
116
|
}`}
|
|
117
117
|
>
|
|
@@ -200,7 +200,7 @@ export default function VideoBlockEditor({ block }: Props) {
|
|
|
200
200
|
onClick={() => updateResponsive("width", opt.value)}
|
|
201
201
|
className={`flex-1 rounded border py-1 text-xs transition-colors ${
|
|
202
202
|
effectiveWidth === opt.value
|
|
203
|
-
? "border-[#
|
|
203
|
+
? "border-[#3580f9] bg-[#3580f9]/20 text-neutral-900"
|
|
204
204
|
: "border-neutral-200 bg-white text-neutral-500 hover:border-neutral-600"
|
|
205
205
|
}`}
|
|
206
206
|
>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export { default as TextBlockEditor } from "./TextBlockEditor";
|
|
2
|
-
export { default as ImageBlockEditor } from "./ImageBlockEditor";
|
|
3
|
-
export { default as ImageGridBlockEditor } from "./ImageGridBlockEditor";
|
|
4
|
-
export { default as VideoBlockEditor } from "./VideoBlockEditor";
|
|
5
|
-
export { default as SpacerBlockEditor } from "./SpacerBlockEditor";
|
|
6
|
-
export { default as ButtonBlockEditor } from "./ButtonBlockEditor";
|
|
7
|
-
export { default as ProjectGridEditor } from "./ProjectGridEditor";
|
|
8
|
-
export { default as ProjectCarouselBlockEditor } from "./ProjectCarouselBlockEditor";
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
1
|
+
export { default as TextBlockEditor } from "./TextBlockEditor";
|
|
2
|
+
export { default as ImageBlockEditor } from "./ImageBlockEditor";
|
|
3
|
+
export { default as ImageGridBlockEditor } from "./ImageGridBlockEditor";
|
|
4
|
+
export { default as VideoBlockEditor } from "./VideoBlockEditor";
|
|
5
|
+
export { default as SpacerBlockEditor } from "./SpacerBlockEditor";
|
|
6
|
+
export { default as ButtonBlockEditor } from "./ButtonBlockEditor";
|
|
7
|
+
export { default as ProjectGridEditor } from "./ProjectGridEditor";
|
|
8
|
+
export { default as ProjectCarouselBlockEditor } from "./ProjectCarouselBlockEditor";
|
|
9
|
+
export { default as MarqueeBlockEditor } from "./MarqueeBlockEditor";
|
|
10
|
+
export { SettingsField, SettingsSection, StyledSelect, StyledInput, StyledCheckbox } from "./shared";
|
|
11
|
+
export { getSpacerPx } from "./SpacerBlockEditor";
|
|
@@ -6,6 +6,7 @@ import { hasOverride } from "../../../lib/builder/responsive";
|
|
|
6
6
|
import type { DeviceViewport } from "../../../lib/builder/types";
|
|
7
7
|
import type { ContentBlock } from "../../../lib/sanity/types";
|
|
8
8
|
import AssetBrowser from "../AssetBrowser";
|
|
9
|
+
import { BubbleTooltip } from "../BubbleIcons";
|
|
9
10
|
|
|
10
11
|
// ============================================
|
|
11
12
|
// Shared CSS classes — Framer-style design system
|
|
@@ -13,11 +14,11 @@ import AssetBrowser from "../AssetBrowser";
|
|
|
13
14
|
|
|
14
15
|
/** Base input class: gray bg, no border, border on focus */
|
|
15
16
|
const INPUT_CLASS =
|
|
16
|
-
"w-full rounded-lg border border-transparent bg-[#f5f5f5] px-2.5 py-[7px] text-xs text-neutral-900 font-normal outline-none transition-all hover:bg-[#efefef] focus:bg-white focus:border-[#
|
|
17
|
+
"w-full rounded-lg border border-transparent bg-[#f5f5f5] px-2.5 py-[7px] text-xs text-neutral-900 font-normal outline-none transition-all hover:bg-[#efefef] focus:bg-white focus:border-[#3580f9] focus:shadow-[0_0_0_3px_rgba(53, 128, 249,0.06)]";
|
|
17
18
|
|
|
18
19
|
/** Select class — same as input */
|
|
19
20
|
const SELECT_CLASS =
|
|
20
|
-
"w-full rounded-lg border border-transparent bg-[#f5f5f5] px-2.5 py-[7px] text-xs text-neutral-900 font-normal outline-none transition-all hover:bg-[#efefef] focus:bg-white focus:border-[#
|
|
21
|
+
"w-full rounded-lg border border-transparent bg-[#f5f5f5] px-2.5 py-[7px] text-xs text-neutral-900 font-normal outline-none transition-all hover:bg-[#efefef] focus:bg-white focus:border-[#3580f9] focus:shadow-[0_0_0_3px_rgba(53, 128, 249,0.06)]";
|
|
21
22
|
|
|
22
23
|
// ============================================
|
|
23
24
|
// Hooks
|
|
@@ -47,8 +48,8 @@ export function ViewportBadge() {
|
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
return (
|
|
50
|
-
<div className="flex items-center gap-1.5 px-3 py-1.5 mb-2 rounded-lg bg-[#
|
|
51
|
-
<span className="text-[11px] font-medium text-[#
|
|
51
|
+
<div className="flex items-center gap-1.5 px-3 py-1.5 mb-2 rounded-lg bg-[#3580f9]/8 border border-[#3580f9]/15">
|
|
52
|
+
<span className="text-[11px] font-medium text-[#3580f9]">
|
|
52
53
|
Editing {labels[viewport]} overrides
|
|
53
54
|
</span>
|
|
54
55
|
</div>
|
|
@@ -88,7 +89,7 @@ export function ResponsiveField({
|
|
|
88
89
|
<span className="block text-[9px] text-neutral-300 italic mt-0.5">inherited</span>
|
|
89
90
|
)}
|
|
90
91
|
{isOverridden && (
|
|
91
|
-
<span className="block text-[9px] text-[#
|
|
92
|
+
<span className="block text-[9px] text-[#3580f9] mt-0.5">overridden</span>
|
|
92
93
|
)}
|
|
93
94
|
</label>
|
|
94
95
|
<div className="flex-1 min-w-0">
|
|
@@ -291,7 +292,7 @@ export function StyledCheckbox({
|
|
|
291
292
|
type="button"
|
|
292
293
|
onClick={() => onChange(!checked)}
|
|
293
294
|
className={`relative w-8 h-[18px] rounded-full transition-colors ${
|
|
294
|
-
checked ? "bg-[#
|
|
295
|
+
checked ? "bg-[#3580f9]" : "bg-neutral-200 group-hover:bg-neutral-300"
|
|
295
296
|
}`}
|
|
296
297
|
>
|
|
297
298
|
<span
|
|
@@ -338,10 +339,11 @@ export function AssetPathInput({
|
|
|
338
339
|
<button
|
|
339
340
|
type="button"
|
|
340
341
|
onClick={() => setBrowserOpen(true)}
|
|
341
|
-
className="shrink-0 rounded-lg bg-[#f5f5f5] px-2.5 py-[7px] text-[11px] text-neutral-500 hover:text-neutral-900 hover:bg-[#efefef] transition-colors"
|
|
342
|
-
|
|
342
|
+
className="group/bb relative shrink-0 rounded-lg bg-[#f5f5f5] px-2.5 py-[7px] text-[11px] text-neutral-500 hover:text-neutral-900 hover:bg-[#efefef] transition-colors"
|
|
343
|
+
aria-label="Browse assets"
|
|
343
344
|
>
|
|
344
345
|
Browse
|
|
346
|
+
<BubbleTooltip>Browse assets</BubbleTooltip>
|
|
345
347
|
</button>
|
|
346
348
|
</div>
|
|
347
349
|
<AssetBrowser
|