mirror of
https://github.com/jnsahaj/tweakcn.git
synced 2026-08-28 23:02:07 +08:00
cde8adc6bd
* chore: update shadcn/ui components to v3.8.5 and migrate to unified radix-ui - Update shadcn CLI from 2.5.0 to 3.8.5 - Update 45 standard UI components to latest versions - Migrate from 28 individual @radix-ui/react-* packages to unified radix-ui - Update react-resizable-panels from v2 to v4 (direction→orientation, onLayout→onLayoutChanged, ref→panelRef, numeric sizes→percentage strings) - Fix revola.tsx runtime error by sharing shouldUseDialog via React context instead of independent useMediaQuery calls in each sub-component - Preserve custom components: sidebar.tsx, base-ui-tabs.tsx, revola.tsx - Re-apply button accent variant after component update - Fix direct Radix imports in theme-toggle, user-profile-dropdown, data-table-view-options - Fix registryItemSchema import (shadcn/registry→shadcn/schema) - Fix ScrollArea viewPortClassName removal (use data-slot CSS selector) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve CSS specificity regressions from shadcn v3.8.5 update - button: remove has-[>svg]:px-* variants that override custom padding, restore lg px-8 - dialog: revert sm:max-w-lg to max-w-lg so consumer max-w overrides work - separator: use JS conditionals instead of data-attribute variants for orientation - tabs: restore old TabsTrigger styles to prevent dark mode border overrides - select consumers: use size="sm" prop instead of className h-8 override Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
173 lines
5.4 KiB
TypeScript
173 lines
5.4 KiB
TypeScript
"use client";
|
|
import { Monitor, Smartphone, Tablet } from "lucide-react";
|
|
import React from "react";
|
|
import { PanelImperativeHandle } from "react-resizable-panels";
|
|
|
|
import { ComponentErrorBoundary } from "@/components/error-boundary";
|
|
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type BlockViewerContext = {
|
|
resizablePanelRef: React.RefObject<PanelImperativeHandle | null>;
|
|
toggleValue: string;
|
|
setToggleValue: (value: string) => void;
|
|
};
|
|
|
|
const BlockViewerContext = React.createContext<BlockViewerContext | null>(null);
|
|
|
|
function useBlockViewer() {
|
|
const context = React.useContext(BlockViewerContext);
|
|
if (!context) {
|
|
throw new Error("useBlockViewer must be used within a BlockViewerProvider.");
|
|
}
|
|
return context;
|
|
}
|
|
|
|
function BlockViewerProvider({ children }: { children: React.ReactNode }) {
|
|
const resizablePanelRef = React.useRef<PanelImperativeHandle>(null);
|
|
const [toggleValue, setToggleValue] = React.useState("100");
|
|
|
|
return (
|
|
<BlockViewerContext.Provider
|
|
value={{
|
|
resizablePanelRef,
|
|
toggleValue,
|
|
setToggleValue,
|
|
}}
|
|
>
|
|
{children}
|
|
</BlockViewerContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function BlockViewer({
|
|
className,
|
|
name,
|
|
children,
|
|
...props
|
|
}: React.ComponentProps<"div"> & {
|
|
name: string;
|
|
}) {
|
|
return (
|
|
<BlockViewerProvider>
|
|
<div
|
|
data-slot="block-viewer"
|
|
className={cn(
|
|
"group/block-viewer bg-background @container isolate flex size-full min-w-0 flex-col overflow-clip",
|
|
className
|
|
)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</div>
|
|
</BlockViewerProvider>
|
|
);
|
|
}
|
|
|
|
export function BlockViewerToolbar({
|
|
name,
|
|
className,
|
|
toolbarControls,
|
|
}: React.ComponentProps<"div"> & {
|
|
name: string;
|
|
className?: string;
|
|
toolbarControls?: React.ReactNode;
|
|
}) {
|
|
const { resizablePanelRef, toggleValue, setToggleValue } = useBlockViewer();
|
|
|
|
return (
|
|
<div data-slot="block-viewer-toolbar" className={cn("h-12 w-full border-b p-2", className)}>
|
|
<div className="flex size-full items-center justify-between gap-4">
|
|
<div className="flex-1">
|
|
{!!toolbarControls ? (
|
|
toolbarControls
|
|
) : (
|
|
<span className="text-sm font-medium capitalize">{name}</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className="hidden items-center justify-between lg:group-has-data-[slot=block-viewer-display]/block-viewer:flex">
|
|
<ToggleGroup
|
|
className="bg-background flex gap-0.5 rounded-lg border p-0.5"
|
|
type="single"
|
|
value={toggleValue}
|
|
onValueChange={(value) => {
|
|
if (value && resizablePanelRef?.current) {
|
|
resizablePanelRef.current.resize(parseInt(value));
|
|
setToggleValue(value);
|
|
}
|
|
}}
|
|
>
|
|
<ToggleGroupItem value="100" className="flex size-7 items-center justify-center p-1">
|
|
<Monitor className="size-4" />
|
|
</ToggleGroupItem>
|
|
|
|
<ToggleGroupItem value="60" className="flex size-7 items-center justify-center p-1">
|
|
<Tablet className="size-4" />
|
|
</ToggleGroupItem>
|
|
|
|
<ToggleGroupItem value="30" className="flex size-7 items-center justify-center p-1">
|
|
<Smartphone className="size-4" />
|
|
</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function BlockViewerDisplay({
|
|
name,
|
|
className,
|
|
children,
|
|
...props
|
|
}: React.ComponentProps<"div"> & {
|
|
name: string;
|
|
}) {
|
|
const { resizablePanelRef, setToggleValue } = useBlockViewer();
|
|
|
|
// Auto-resize to full width when screen goes under lg breakpoint (1024px)
|
|
React.useEffect(() => {
|
|
const mql = window.matchMedia("(max-width: 1023px)");
|
|
const resizePanel = () => {
|
|
if (window.innerWidth < 1024 && resizablePanelRef?.current) {
|
|
resizablePanelRef.current.resize(100);
|
|
setToggleValue("100");
|
|
}
|
|
};
|
|
|
|
resizePanel();
|
|
mql.addEventListener("change", resizePanel);
|
|
return () => mql.removeEventListener("change", resizePanel);
|
|
}, [resizablePanelRef, setToggleValue]);
|
|
|
|
return (
|
|
<ComponentErrorBoundary name={name}>
|
|
<div
|
|
id={name}
|
|
data-slot="block-viewer-display"
|
|
data-name={name.toLowerCase()}
|
|
className={cn("size-full min-h-0 gap-4 overflow-hidden", className)}
|
|
{...props}
|
|
>
|
|
<ResizablePanelGroup orientation="horizontal" className="relative isolate z-10 size-full">
|
|
<div className="bg-muted absolute inset-0 right-3 [background-image:radial-gradient(var(--muted-foreground),transparent_1px)] [background-size:1rem_1rem] opacity-60 dark:opacity-35" />
|
|
|
|
<ResizablePanel
|
|
panelRef={resizablePanelRef}
|
|
className="bg-background relative min-w-[350px] lg:aspect-auto"
|
|
defaultSize="100%"
|
|
minSize="30%"
|
|
>
|
|
{children}
|
|
</ResizablePanel>
|
|
|
|
<ResizableHandle className="after:bg-border relative inset-x-0 mx-auto hidden w-3 border-l bg-transparent after:absolute after:top-1/2 after:h-8 after:w-[4px] after:-translate-y-1/2 after:rounded-full after:transition-all hover:after:h-12 active:after:h-12 lg:block" />
|
|
<ResizablePanel defaultSize="0%" minSize="0%" />
|
|
</ResizablePanelGroup>
|
|
</div>
|
|
</ComponentErrorBoundary>
|
|
);
|
|
}
|