style: re-design colors in text properties panel
This commit is contained in:
parent
ebe1b38bdf
commit
f9170dcf09
|
|
@ -6,7 +6,6 @@ import { useTimelineStore } from "@/stores/timeline-store";
|
|||
import { Slider } from "@/components/ui/slider";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { useState, useRef } from "react";
|
||||
import { PanelBaseView } from "@/components/editor/panel-base-view";
|
||||
import {
|
||||
|
|
@ -19,6 +18,14 @@ import {
|
|||
PropertyItemLabel,
|
||||
PropertyItemValue,
|
||||
} from "./property-item";
|
||||
import { ColorPicker } from "@/components/ui/color-picker";
|
||||
import { cn, uppercase } from "@/lib/utils";
|
||||
import { Grid2x2 } from "lucide-react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
export function TextProperties({
|
||||
element,
|
||||
|
|
@ -29,7 +36,7 @@ export function TextProperties({
|
|||
}) {
|
||||
const { updateTextElement } = useTimelineStore();
|
||||
const { activeTab, setActiveTab } = useTextPropertiesStore();
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
// Local state for input values to allow temporary empty/invalid states
|
||||
const [fontSizeInput, setFontSizeInput] = useState(
|
||||
element.fontSize.toString()
|
||||
|
|
@ -118,6 +125,7 @@ export function TextProperties({
|
|||
onValueChange={(v) => {
|
||||
if (isTextPropertiesTab(v)) setActiveTab(v);
|
||||
}}
|
||||
ref={containerRef}
|
||||
tabs={TEXT_PROPERTIES_TABS.map((t) => ({
|
||||
value: t.value,
|
||||
label: t.label,
|
||||
|
|
@ -250,7 +258,7 @@ export function TextProperties({
|
|||
max={300}
|
||||
onChange={(e) => handleFontSizeChange(e.target.value)}
|
||||
onBlur={handleFontSizeBlur}
|
||||
className="w-12 px-2 !text-xs h-7 rounded-sm text-center
|
||||
className="w-12 px-2 !text-xs h-7 rounded-sm text-center bg-panel-accent
|
||||
[appearance:textfield]
|
||||
[&::-webkit-outer-spin-button]:appearance-none
|
||||
[&::-webkit-inner-spin-button]:appearance-none"
|
||||
|
|
@ -261,14 +269,16 @@ export function TextProperties({
|
|||
<PropertyItem direction="column">
|
||||
<PropertyItemLabel>Color</PropertyItemLabel>
|
||||
<PropertyItemValue>
|
||||
<Input
|
||||
type="color"
|
||||
value={element.color || "#ffffff"}
|
||||
onChange={(e) => {
|
||||
const color = e.target.value;
|
||||
updateTextElement(trackId, element.id, { color });
|
||||
<ColorPicker
|
||||
value={uppercase(
|
||||
(element.color || "FFFFFF").replace("#", "")
|
||||
)}
|
||||
onChange={(color) => {
|
||||
updateTextElement(trackId, element.id, {
|
||||
color: `#${color}`,
|
||||
});
|
||||
}}
|
||||
className="w-full cursor-pointer rounded-full"
|
||||
containerRef={containerRef}
|
||||
/>
|
||||
</PropertyItemValue>
|
||||
</PropertyItem>
|
||||
|
|
@ -296,7 +306,7 @@ export function TextProperties({
|
|||
max={100}
|
||||
onChange={(e) => handleOpacityChange(e.target.value)}
|
||||
onBlur={handleOpacityBlur}
|
||||
className="w-12 !text-xs h-7 rounded-sm text-center
|
||||
className="w-12 !text-xs h-7 rounded-sm text-center bg-panel-accent
|
||||
[appearance:textfield]
|
||||
[&::-webkit-outer-spin-button]:appearance-none
|
||||
[&::-webkit-inner-spin-button]:appearance-none"
|
||||
|
|
@ -305,34 +315,51 @@ export function TextProperties({
|
|||
</PropertyItemValue>
|
||||
</PropertyItem>
|
||||
<PropertyItem direction="column">
|
||||
<div className="flex items-center justify-between">
|
||||
<PropertyItemLabel>Background</PropertyItemLabel>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
id="transparent-bg-toggle"
|
||||
checked={element.backgroundColor === "transparent"}
|
||||
onCheckedChange={handleTransparentToggle}
|
||||
/>
|
||||
<label
|
||||
htmlFor="transparent-bg-toggle"
|
||||
className="text-sm font-medium"
|
||||
>
|
||||
Transparent
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<PropertyItemLabel>Background</PropertyItemLabel>
|
||||
<PropertyItemValue>
|
||||
<Input
|
||||
type="color"
|
||||
value={
|
||||
element.backgroundColor === "transparent"
|
||||
? lastSelectedColor.current
|
||||
: element.backgroundColor || "#000000"
|
||||
}
|
||||
onChange={(e) => handleColorChange(e.target.value)}
|
||||
className="w-full cursor-pointer rounded-full"
|
||||
disabled={element.backgroundColor === "transparent"}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<ColorPicker
|
||||
value={uppercase(
|
||||
element.backgroundColor === "transparent"
|
||||
? lastSelectedColor.current.replace("#", "")
|
||||
: (element.backgroundColor || "#000000").replace(
|
||||
"#",
|
||||
""
|
||||
)
|
||||
)}
|
||||
onChange={(color) => handleColorChange(`#${color}`)}
|
||||
containerRef={containerRef}
|
||||
className={
|
||||
element.backgroundColor === "transparent"
|
||||
? "opacity-50 pointer-events-none"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() =>
|
||||
handleTransparentToggle(
|
||||
element.backgroundColor !== "transparent"
|
||||
)
|
||||
}
|
||||
className="size-9 rounded-full bg-panel-accent p-0 overflow-hidden"
|
||||
>
|
||||
<Grid2x2
|
||||
className={cn(
|
||||
"text-foreground",
|
||||
element.backgroundColor === "transparent" &&
|
||||
"text-primary"
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Transparent background</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</PropertyItemValue>
|
||||
</PropertyItem>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,325 @@
|
|||
import * as React from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { cn } from "../../lib/utils";
|
||||
import { Input } from "./input";
|
||||
|
||||
interface ColorPickerProps {
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
className?: string;
|
||||
containerRef?: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const hexToHsv = (hex: string) => {
|
||||
const r = parseInt(hex.slice(0, 2), 16) / 255;
|
||||
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
||||
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
||||
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
const diff = max - min;
|
||||
|
||||
let h = 0;
|
||||
let s = max === 0 ? 0 : diff / max;
|
||||
let v = max;
|
||||
|
||||
if (diff !== 0) {
|
||||
switch (max) {
|
||||
case r:
|
||||
h = ((g - b) / diff) % 6;
|
||||
break;
|
||||
case g:
|
||||
h = (b - r) / diff + 2;
|
||||
break;
|
||||
case b:
|
||||
h = (r - g) / diff + 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [h * 60, s, v];
|
||||
};
|
||||
|
||||
const hsvToHex = (h: number, s: number, v: number) => {
|
||||
const c = v * s;
|
||||
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
||||
const m = v - c;
|
||||
|
||||
let r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
|
||||
if (h >= 0 && h < 60) {
|
||||
r = c;
|
||||
g = x;
|
||||
b = 0;
|
||||
} else if (h >= 60 && h < 120) {
|
||||
r = x;
|
||||
g = c;
|
||||
b = 0;
|
||||
} else if (h >= 120 && h < 180) {
|
||||
r = 0;
|
||||
g = c;
|
||||
b = x;
|
||||
} else if (h >= 180 && h < 240) {
|
||||
r = 0;
|
||||
g = x;
|
||||
b = c;
|
||||
} else if (h >= 240 && h < 300) {
|
||||
r = x;
|
||||
g = 0;
|
||||
b = c;
|
||||
} else if (h >= 300 && h < 360) {
|
||||
r = c;
|
||||
g = 0;
|
||||
b = x;
|
||||
}
|
||||
|
||||
r = Math.round((r + m) * 255);
|
||||
g = Math.round((g + m) * 255);
|
||||
b = Math.round((b + m) * 255);
|
||||
|
||||
return [r, g, b].map((x) => x.toString(16).padStart(2, "0")).join("");
|
||||
};
|
||||
|
||||
const ColorPicker = React.forwardRef<HTMLDivElement, ColorPickerProps>(
|
||||
({ className, value = "FFFFFF", onChange, containerRef, ...props }, ref) => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const [isDragging, setIsDragging] = React.useState<
|
||||
"saturation" | "hue" | null
|
||||
>(null);
|
||||
const [pickerPosition, setPickerPosition] = React.useState({
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
});
|
||||
const pickerRef = React.useRef<HTMLDivElement>(null);
|
||||
const saturationRef = React.useRef<HTMLDivElement>(null);
|
||||
const hueRef = React.useRef<HTMLDivElement>(null);
|
||||
const triggerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const [h, s, v] = hexToHsv(value);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
pickerRef.current &&
|
||||
!pickerRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () =>
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!isDragging) return;
|
||||
|
||||
if (isDragging === "saturation" && saturationRef.current) {
|
||||
const rect = saturationRef.current.getBoundingClientRect();
|
||||
const x = Math.max(
|
||||
0,
|
||||
Math.min(1, (e.clientX - rect.left) / rect.width)
|
||||
);
|
||||
const y = Math.max(
|
||||
0,
|
||||
Math.min(1, (e.clientY - rect.top) / rect.height)
|
||||
);
|
||||
|
||||
const newS = x;
|
||||
const newV = 1 - y;
|
||||
const newHex = hsvToHex(h, newS, newV);
|
||||
onChange?.(newHex);
|
||||
}
|
||||
|
||||
if (isDragging === "hue" && hueRef.current) {
|
||||
const rect = hueRef.current.getBoundingClientRect();
|
||||
const x = Math.max(
|
||||
0,
|
||||
Math.min(1, (e.clientX - rect.left) / rect.width)
|
||||
);
|
||||
const newH = x * 360;
|
||||
const newHex = hsvToHex(newH, s, v);
|
||||
onChange?.(newHex);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setIsDragging(null);
|
||||
};
|
||||
|
||||
if (isDragging) {
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
return () => {
|
||||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
}
|
||||
}, [isDragging, h, s, v, onChange]);
|
||||
|
||||
const handleSaturationMouseDown = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
setIsDragging("saturation");
|
||||
|
||||
const rect = saturationRef.current!.getBoundingClientRect();
|
||||
const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
|
||||
const y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height));
|
||||
const newS = x;
|
||||
const newV = 1 - y;
|
||||
const newHex = hsvToHex(h, newS, newV);
|
||||
onChange?.(newHex);
|
||||
};
|
||||
|
||||
const handleHueMouseDown = (e: React.MouseEvent) => {
|
||||
setIsDragging("hue");
|
||||
|
||||
const rect = hueRef.current!.getBoundingClientRect();
|
||||
const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
|
||||
const newH = x * 360;
|
||||
const newHex = hsvToHex(newH, s, v);
|
||||
onChange?.(newHex);
|
||||
};
|
||||
|
||||
const [inputValue, setInputValue] = React.useState(value);
|
||||
|
||||
React.useEffect(() => {
|
||||
setInputValue(value);
|
||||
}, [value]);
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const hex = e.target.value.replace("#", "");
|
||||
setInputValue(hex);
|
||||
};
|
||||
|
||||
const handleInputBlur = () => {
|
||||
onChange?.(inputValue);
|
||||
};
|
||||
|
||||
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter") {
|
||||
onChange?.(inputValue);
|
||||
e.currentTarget.blur();
|
||||
}
|
||||
};
|
||||
|
||||
const saturationStyle = {
|
||||
background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, hsl(${h}, 100%, 50%))`,
|
||||
};
|
||||
|
||||
const hueStyle = {
|
||||
background:
|
||||
"linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%)",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"bg-panel-accent h-9 rounded-full flex items-center gap-2 px-[0.45rem]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
ref={triggerRef}
|
||||
className="size-6 rounded-full cursor-pointer hover:ring-2 hover:ring-white/20 transition-all"
|
||||
style={{ backgroundColor: `#${value}` }}
|
||||
onClick={() => {
|
||||
if (!isOpen && triggerRef.current && containerRef?.current) {
|
||||
const containerRect =
|
||||
containerRef.current.getBoundingClientRect();
|
||||
|
||||
setPickerPosition({
|
||||
right: window.innerWidth - containerRect.left - 8,
|
||||
bottom: window.innerHeight - containerRect.bottom,
|
||||
});
|
||||
}
|
||||
setIsOpen(!isOpen);
|
||||
}}
|
||||
/>
|
||||
<div className="flex-1 flex items-center">
|
||||
<Input
|
||||
className="bg-transparent p-0 !ring-0 !ring-offset-0 !border-0 "
|
||||
containerClassName="w-full"
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onBlur={handleInputBlur}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isOpen &&
|
||||
createPortal(
|
||||
<div
|
||||
ref={pickerRef}
|
||||
className="fixed z-50 p-4 bg-popover border border-border rounded-lg shadow-lg select-none"
|
||||
style={{
|
||||
right: pickerPosition.right,
|
||||
bottom: pickerPosition.bottom,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={saturationRef}
|
||||
className="relative w-48 h-32 cursor-crosshair mb-3"
|
||||
style={saturationStyle}
|
||||
onMouseDown={handleSaturationMouseDown}
|
||||
>
|
||||
<ColorCircle
|
||||
size="sm"
|
||||
position={{ left: `${s * 100}%`, top: `${(1 - v) * 100}%` }}
|
||||
color={`#${value}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={hueRef}
|
||||
className="relative w-48 h-4 rounded-lg cursor-pointer"
|
||||
style={hueStyle}
|
||||
onMouseDown={handleHueMouseDown}
|
||||
>
|
||||
<ColorCircle
|
||||
size="md"
|
||||
position={{ left: `${(h / 360) * 100}%`, top: "50%" }}
|
||||
color={`#${value}`}
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
ColorPicker.displayName = "ColorPicker";
|
||||
|
||||
const ColorCircle = ({
|
||||
size,
|
||||
position,
|
||||
color,
|
||||
}: {
|
||||
size: "sm" | "md";
|
||||
position: { left: string; top: string };
|
||||
color: string;
|
||||
}) => (
|
||||
<div
|
||||
className={`absolute border-3 border-accent rounded-full shadow-lg pointer-events-none ${
|
||||
size === "sm" ? "w-3 h-3" : "w-4 h-4"
|
||||
}`}
|
||||
style={{
|
||||
left: position.left,
|
||||
top: position.top,
|
||||
transform: "translate(-50%, -50%)",
|
||||
backgroundColor: color,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export { ColorPicker };
|
||||
|
|
@ -7,6 +7,10 @@ export function cn(...inputs: ClassValue[]) {
|
|||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function uppercase(str: string) {
|
||||
return str.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a UUID v4 string
|
||||
* Uses crypto.randomUUID() if available, otherwise falls back to a custom implementation
|
||||
|
|
|
|||
8
bun.lock
8
bun.lock
|
|
@ -533,7 +533,7 @@
|
|||
|
||||
"@tailwindcss/typography": ["@tailwindcss/typography@0.5.16", "", { "dependencies": { "lodash.castarray": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.merge": "^4.6.2", "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="],
|
||||
"@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
|
||||
|
||||
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
||||
|
||||
|
|
@ -1257,6 +1257,8 @@
|
|||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@types/bun/bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],
|
||||
|
||||
"better-auth/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"motion/framer-motion": ["framer-motion@12.23.6", "", { "dependencies": { "motion-dom": "^12.23.6", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-dsJ389QImVE3lQvM8Mnk99/j8tiZDM/7706PCqvkQ8sSCnpmWxsgX+g0lj7r5OBVL0U36pIecCTBoIWcM2RuKw=="],
|
||||
|
|
@ -1323,6 +1325,8 @@
|
|||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="],
|
||||
|
||||
"@types/bun/bun-types/@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="],
|
||||
|
||||
"motion/framer-motion/motion-dom": ["motion-dom@12.23.6", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-G2w6Nw7ZOVSzcQmsdLc0doMe64O/Sbuc2bVAbgMz6oP/6/pRStKRiVRV4bQfHp5AHYAKEGhEdVHTM+R3FDgi5w=="],
|
||||
|
||||
"motion/framer-motion/motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="],
|
||||
|
|
@ -1351,6 +1355,8 @@
|
|||
|
||||
"opencut/next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
||||
|
||||
"@types/bun/bun-types/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
|
||||
|
||||
"opencut/next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue