Merge branch 'main' into main
This commit is contained in:
commit
f4ecafad02
27
backend/access_token.py
Normal file
27
backend/access_token.py
Normal file
@ -0,0 +1,27 @@
|
||||
import json
|
||||
import os
|
||||
import httpx
|
||||
|
||||
|
||||
async def validate_access_token(access_code: str):
|
||||
async with httpx.AsyncClient() as client:
|
||||
url = (
|
||||
"https://backend.buildpicoapps.com/screenshot_to_code/validate_access_token"
|
||||
)
|
||||
data = json.dumps(
|
||||
{
|
||||
"access_code": access_code,
|
||||
"secret": os.environ.get("PICO_BACKEND_SECRET"),
|
||||
}
|
||||
)
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
response = await client.post(url, content=data, headers=headers)
|
||||
response_data = response.json()
|
||||
|
||||
if response_data["success"]:
|
||||
print("Access token is valid.")
|
||||
return True
|
||||
else:
|
||||
print(f"Access token validation failed: {response_data['failure_reason']}")
|
||||
return False
|
||||
@ -1,6 +1,5 @@
|
||||
# Load environment variables first
|
||||
from dotenv import load_dotenv
|
||||
from pydantic import BaseModel
|
||||
|
||||
load_dotenv()
|
||||
|
||||
@ -16,6 +15,7 @@ from mock import mock_completion
|
||||
from image_generation import create_alt_url_mapping, generate_images
|
||||
from prompts import assemble_prompt
|
||||
from routes import screenshot
|
||||
from access_token import validate_access_token
|
||||
|
||||
app = FastAPI(openapi_url=None, docs_url=None, redoc_url=None)
|
||||
|
||||
@ -79,6 +79,20 @@ async def stream_code(websocket: WebSocket):
|
||||
|
||||
# Get the OpenAI API key from the request. Fall back to environment variable if not provided.
|
||||
# If neither is provided, we throw an error.
|
||||
openai_api_key = None
|
||||
if "accessCode" in params and params["accessCode"]:
|
||||
print("Access code - using platform API key")
|
||||
if await validate_access_token(params["accessCode"]):
|
||||
openai_api_key = os.environ.get("PLATFORM_OPENAI_API_KEY")
|
||||
else:
|
||||
await websocket.send_json(
|
||||
{
|
||||
"type": "error",
|
||||
"value": "Invalid access code or you're out of credits. Please try again.",
|
||||
}
|
||||
)
|
||||
return
|
||||
else:
|
||||
if params["openAiApiKey"]:
|
||||
openai_api_key = params["openAiApiKey"]
|
||||
print("Using OpenAI API key from client-side settings dialog")
|
||||
|
||||
@ -21,6 +21,34 @@
|
||||
<%- injectHead %>
|
||||
|
||||
<title>Screenshot to Code</title>
|
||||
|
||||
<!-- Open Graph Meta Tags -->
|
||||
<meta property="og:title" content="Screenshot to Code" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Convert any screenshot or design to clean code"
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://screenshottocode.com/brand/twitter-summary-card.png"
|
||||
/>
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="628" />
|
||||
<meta property="og:url" content="https://screenshottocode.com" />
|
||||
<meta property="og:type" content="website" />
|
||||
<!-- Twitter Card tags -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:site" content="@picoapps" />
|
||||
<!-- Keep in sync with og:title, og:description and og:image -->
|
||||
<meta name="twitter:title" content="Screenshot to Code" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Convert any screenshot or design to clean code"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://screenshottocode.com/brand/twitter-summary-card.png"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
|
||||
BIN
frontend/public/brand/twitter-summary-card.png
Normal file
BIN
frontend/public/brand/twitter-summary-card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 330 KiB |
@ -51,12 +51,13 @@ function App() {
|
||||
isImageGenerationEnabled: true,
|
||||
editorTheme: EditorTheme.COBALT,
|
||||
isTermOfServiceAccepted: false,
|
||||
accessCode: null,
|
||||
},
|
||||
"setting"
|
||||
);
|
||||
const [outputSettings, setOutputSettings] = useState<OutputSettings>({
|
||||
css: CSSOption.TAILWIND,
|
||||
js: JSFrameworkOption.VANILLA,
|
||||
js: JSFrameworkOption.NO_FRAMEWORK,
|
||||
});
|
||||
const [shouldIncludeResultImage, setShouldIncludeResultImage] =
|
||||
useState<boolean>(false);
|
||||
@ -169,9 +170,8 @@ function App() {
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-2 dark:bg-black dark:text-white">
|
||||
{IS_RUNNING_ON_CLOUD && <PicoBadge />}
|
||||
{IS_RUNNING_ON_CLOUD && <PicoBadge settings={settings} />}
|
||||
{IS_RUNNING_ON_CLOUD && (
|
||||
<TermsOfServiceDialog
|
||||
open={!settings.isTermOfServiceAccepted}
|
||||
@ -180,24 +180,23 @@ function App() {
|
||||
)}
|
||||
<div className="lg:fixed lg:inset-y-0 lg:z-40 lg:flex lg:w-96 lg:flex-col">
|
||||
<div className="flex grow flex-col gap-y-2 overflow-y-auto border-r border-gray-200 bg-white px-6 dark:bg-zinc-950 dark:text-white">
|
||||
<div className="flex items-center justify-between mt-10">
|
||||
<div className="flex items-center justify-between mt-10 mb-2">
|
||||
<h1 className="text-2xl ">Screenshot to Code</h1>
|
||||
<SettingsDialog settings={settings} setSettings={setSettings} />
|
||||
</div>
|
||||
{appState === AppState.INITIAL && (
|
||||
<h2 className="text-sm text-gray-500 mb-2">
|
||||
Drag & drop a screenshot to get started.
|
||||
</h2>
|
||||
)}
|
||||
|
||||
{appState === AppState.INITIAL && (
|
||||
<OutputSettingsSection
|
||||
outputSettings={outputSettings}
|
||||
setOutputSettings={setOutputSettings}
|
||||
shouldDisableUpdates={
|
||||
appState === AppState.CODING || appState === AppState.CODE_READY
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{IS_RUNNING_ON_CLOUD && !settings.openAiApiKey && <OnboardingNote />}
|
||||
{IS_RUNNING_ON_CLOUD &&
|
||||
!(settings.openAiApiKey || settings.accessCode) && (
|
||||
<OnboardingNote />
|
||||
)}
|
||||
|
||||
{(appState === AppState.CODING ||
|
||||
appState === AppState.CODE_READY) && (
|
||||
|
||||
@ -2,8 +2,15 @@ export function OnboardingNote() {
|
||||
return (
|
||||
<div className="flex flex-col space-y-4 bg-green-700 p-2 rounded text-stone-200 text-sm">
|
||||
<span>
|
||||
To use Screenshot to Code, you need an OpenAI API key with GPT4 vision
|
||||
access.{" "}
|
||||
To use Screenshot to Code,{" "}
|
||||
<a
|
||||
className="inline underline hover:opacity-70"
|
||||
href="https://buy.stripe.com/8wM6sre70gBW1nqaEE"
|
||||
target="_blank"
|
||||
>
|
||||
buy some credits (100 generations for $36)
|
||||
</a>{" "}
|
||||
or use your own OpenAI API key with GPT4 vision access.{" "}
|
||||
<a
|
||||
href="https://github.com/abi/screenshot-to-code/blob/main/Troubleshooting.md"
|
||||
className="inline underline hover:opacity-70"
|
||||
@ -11,18 +18,8 @@ export function OnboardingNote() {
|
||||
>
|
||||
Follow these instructions to get yourself a key.
|
||||
</a>{" "}
|
||||
Then, paste it in the Settings dialog (gear icon above).
|
||||
</span>
|
||||
<span>
|
||||
Your key is only stored in your browser. Never stored on our servers. If
|
||||
you prefer, you can also run this app completely locally.{" "}
|
||||
<a
|
||||
href="https://github.com/abi/screenshot-to-code"
|
||||
className="inline underline hover:opacity-70"
|
||||
target="_blank"
|
||||
>
|
||||
See the Github project for instructions.
|
||||
</a>
|
||||
and paste it in the Settings dialog (gear icon above). Your key is only
|
||||
stored in your browser. Never stored on our servers.
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -6,14 +6,10 @@ import {
|
||||
SelectTrigger,
|
||||
} from "./ui/select";
|
||||
import { CSSOption, JSFrameworkOption, OutputSettings } from "../types";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "./ui/accordion";
|
||||
import { capitalize } from "../lib/utils";
|
||||
import toast from "react-hot-toast";
|
||||
import { Label } from "@radix-ui/react-label";
|
||||
import { Button } from "./ui/button";
|
||||
import { Popover, PopoverTrigger, PopoverContent } from "./ui/popover";
|
||||
|
||||
function displayCSSOption(option: CSSOption) {
|
||||
switch (option) {
|
||||
@ -26,6 +22,17 @@ function displayCSSOption(option: CSSOption) {
|
||||
}
|
||||
}
|
||||
|
||||
function displayJSOption(option: JSFrameworkOption) {
|
||||
switch (option) {
|
||||
case JSFrameworkOption.REACT:
|
||||
return "React";
|
||||
case JSFrameworkOption.NO_FRAMEWORK:
|
||||
return "No Framework";
|
||||
default:
|
||||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
function convertStringToCSSOption(option: string) {
|
||||
switch (option) {
|
||||
case "tailwind":
|
||||
@ -37,18 +44,57 @@ function convertStringToCSSOption(option: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function generateDisplayString(settings: OutputSettings) {
|
||||
if (
|
||||
settings.js === JSFrameworkOption.REACT &&
|
||||
settings.css === CSSOption.TAILWIND
|
||||
) {
|
||||
return (
|
||||
<div>
|
||||
Generating <span className="font-bold">React</span> +{" "}
|
||||
<span className="font-bold">Tailwind</span> code
|
||||
</div>
|
||||
);
|
||||
} else if (
|
||||
settings.js === JSFrameworkOption.NO_FRAMEWORK &&
|
||||
settings.css === CSSOption.TAILWIND
|
||||
) {
|
||||
return (
|
||||
<div className="text-gray-800">
|
||||
Generating <span className="font-bold">HTML</span> +{" "}
|
||||
<span className="font-bold">Tailwind</span> code
|
||||
</div>
|
||||
);
|
||||
} else if (
|
||||
settings.js === JSFrameworkOption.NO_FRAMEWORK &&
|
||||
settings.css === CSSOption.BOOTSTRAP
|
||||
) {
|
||||
return (
|
||||
<div>
|
||||
Generating <span className="font-bold">HTML</span> +{" "}
|
||||
<span className="font-bold">Bootstrap</span> code
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
outputSettings: OutputSettings;
|
||||
setOutputSettings: React.Dispatch<React.SetStateAction<OutputSettings>>;
|
||||
shouldDisableUpdates?: boolean;
|
||||
}
|
||||
|
||||
function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) {
|
||||
function OutputSettingsSection({
|
||||
outputSettings,
|
||||
setOutputSettings,
|
||||
shouldDisableUpdates = false,
|
||||
}: Props) {
|
||||
const onCSSValueChange = (value: string) => {
|
||||
setOutputSettings((prev) => {
|
||||
if (prev.js === JSFrameworkOption.REACT) {
|
||||
if (value !== CSSOption.TAILWIND) {
|
||||
toast.error(
|
||||
"React only supports Tailwind CSS. Change JS framework to Vanilla to use Bootstrap."
|
||||
'React only supports Tailwind CSS. Change JS framework to "No Framework" to use Bootstrap.'
|
||||
);
|
||||
}
|
||||
return {
|
||||
@ -79,48 +125,76 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>
|
||||
<div className="flex gap-x-2">Output Settings </div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="gap-y-2 flex flex-col pt-2">
|
||||
<div className="flex justify-between items-center pr-2">
|
||||
<span className="text-sm">CSS</span>
|
||||
<Select value={outputSettings.css} onValueChange={onCSSValueChange}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
{displayCSSOption(outputSettings.css)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value={CSSOption.TAILWIND}>Tailwind</SelectItem>
|
||||
<SelectItem value={CSSOption.BOOTSTRAP}>Bootstrap</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex flex-col gap-y-2 justify-between text-sm">
|
||||
{generateDisplayString(outputSettings)}{" "}
|
||||
{!shouldDisableUpdates && (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline">Customize</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 text-sm">
|
||||
<div className="grid gap-4">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium leading-none">Code Settings</h4>
|
||||
<p className="text-muted-foreground">
|
||||
Customize your code output
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between items-center pr-2">
|
||||
<span className="text-sm">JS Framework</span>
|
||||
<div className="grid gap-2">
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="output-settings-js">JS</Label>
|
||||
<Select
|
||||
value={outputSettings.js}
|
||||
onValueChange={onJsFrameworkChange}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
{capitalize(outputSettings.js)}
|
||||
<SelectTrigger
|
||||
className="col-span-2 h-8"
|
||||
id="output-settings-js"
|
||||
>
|
||||
{displayJSOption(outputSettings.js)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value={JSFrameworkOption.VANILLA}>
|
||||
Vanilla
|
||||
<SelectItem value={JSFrameworkOption.NO_FRAMEWORK}>
|
||||
No Framework
|
||||
</SelectItem>
|
||||
<SelectItem value={JSFrameworkOption.REACT}>
|
||||
React
|
||||
</SelectItem>
|
||||
<SelectItem value={JSFrameworkOption.REACT}>React</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="output-settings-css">CSS</Label>
|
||||
<Select
|
||||
value={outputSettings.css}
|
||||
onValueChange={onCSSValueChange}
|
||||
>
|
||||
<SelectTrigger
|
||||
className="col-span-2 h-8"
|
||||
id="output-settings-css"
|
||||
>
|
||||
{displayCSSOption(outputSettings.css)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value={CSSOption.TAILWIND}>
|
||||
Tailwind
|
||||
</SelectItem>
|
||||
<SelectItem value={CSSOption.BOOTSTRAP}>
|
||||
Bootstrap
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export function PicoBadge() {
|
||||
import { Settings } from "../types";
|
||||
|
||||
export function PicoBadge({ settings }: { settings: Settings }) {
|
||||
return (
|
||||
<>
|
||||
<a
|
||||
@ -12,6 +14,7 @@ export function PicoBadge() {
|
||||
feature requests?
|
||||
</div>
|
||||
</a>
|
||||
{!settings.accessCode && (
|
||||
<a href="https://picoapps.xyz?ref=screenshot-to-code" target="_blank">
|
||||
<div
|
||||
className="fixed z-50 bottom-5 right-5 rounded-md shadow text-black
|
||||
@ -20,6 +23,17 @@ export function PicoBadge() {
|
||||
an open source project by Pico
|
||||
</div>
|
||||
</a>
|
||||
)}
|
||||
{settings.accessCode && (
|
||||
<a href="mailto:support@picoapps.xyz" target="_blank">
|
||||
<div
|
||||
className="fixed z-50 bottom-5 right-5 rounded-md shadow text-black
|
||||
bg-white px-4 text-xs py-3 cursor-pointer"
|
||||
>
|
||||
email support
|
||||
</div>
|
||||
</a>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import { Label } from "./ui/label";
|
||||
import { Input } from "./ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "./ui/select";
|
||||
import { capitalize } from "../lib/utils";
|
||||
import { IS_RUNNING_ON_CLOUD } from "../config";
|
||||
|
||||
interface Props {
|
||||
settings: Settings;
|
||||
@ -38,6 +39,31 @@ function SettingsDialog({ settings, setSettings }: Props) {
|
||||
<DialogHeader>
|
||||
<DialogTitle className="mb-4">Settings</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{/* Access code */}
|
||||
{IS_RUNNING_ON_CLOUD && (
|
||||
<div className="flex flex-col space-y-4 bg-slate-300 p-4 rounded">
|
||||
<Label htmlFor="access-code">
|
||||
<div>Access Code</div>
|
||||
<div className="font-light mt-1 leading-relaxed">
|
||||
Buy an access code.
|
||||
</div>
|
||||
</Label>
|
||||
|
||||
<Input
|
||||
id="access-code"
|
||||
placeholder="Enter your Screenshot to Code access code"
|
||||
value={settings.accessCode || ""}
|
||||
onChange={(e) =>
|
||||
setSettings((s) => ({
|
||||
...s,
|
||||
accessCode: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="image-generation">
|
||||
<div>DALL-E Placeholder Image Generation</div>
|
||||
|
||||
29
frontend/src/components/ui/popover.tsx
Normal file
29
frontend/src/components/ui/popover.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import * as React from "react"
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Popover = PopoverPrimitive.Root
|
||||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
))
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent }
|
||||
@ -9,7 +9,7 @@ export enum CSSOption {
|
||||
}
|
||||
|
||||
export enum JSFrameworkOption {
|
||||
VANILLA = "vanilla",
|
||||
NO_FRAMEWORK = "vanilla",
|
||||
REACT = "react",
|
||||
VUE = "vue",
|
||||
}
|
||||
@ -25,6 +25,7 @@ export interface Settings {
|
||||
isImageGenerationEnabled: boolean;
|
||||
editorTheme: EditorTheme;
|
||||
isTermOfServiceAccepted: boolean; // Only relevant for hosted version
|
||||
accessCode: string | null; // Only relevant for hosted version
|
||||
}
|
||||
|
||||
export enum AppState {
|
||||
|
||||
@ -805,6 +805,28 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
|
||||
"@radix-ui/react-popover@^1.0.7":
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.0.7.tgz#23eb7e3327330cb75ec7b4092d685398c1654e3c"
|
||||
integrity sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-dismissable-layer" "1.0.5"
|
||||
"@radix-ui/react-focus-guards" "1.0.1"
|
||||
"@radix-ui/react-focus-scope" "1.0.4"
|
||||
"@radix-ui/react-id" "1.0.1"
|
||||
"@radix-ui/react-popper" "1.1.3"
|
||||
"@radix-ui/react-portal" "1.0.4"
|
||||
"@radix-ui/react-presence" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-slot" "1.0.2"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.5"
|
||||
|
||||
"@radix-ui/react-popper@1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user