mirror of
https://github.com/FranP-code/Reflecto.git
synced 2025-10-13 00:43:31 +00:00
feat: add NewSpaceDialog component for creating spaces and integrate it into SpacesGrid
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
"desktop:build": "tauri build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui-components/react": "1.0.0-beta.3",
|
||||
"@hookform/resolvers": "^5.1.1",
|
||||
"@tailwindcss/vite": "^4.0.15",
|
||||
"@tanstack/react-form": "^1.12.3",
|
||||
|
||||
130
apps/web/src/components/new-space-dialog.tsx
Normal file
130
apps/web/src/components/new-space-dialog.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Plus } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ColorPicker, type ColorValue } from "@/components/ui/color-picker";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { createSpace } from "@/lib/appwrite-db";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
|
||||
type NewSpaceDialogProps = {
|
||||
onSpaceCreated?: () => void;
|
||||
triggerButton?: React.ReactNode;
|
||||
};
|
||||
|
||||
export function NewSpaceDialog({
|
||||
onSpaceCreated,
|
||||
triggerButton,
|
||||
}: NewSpaceDialogProps) {
|
||||
const [_open, setOpen] = useState(false);
|
||||
const [title, setTitle] = useState("");
|
||||
const [color, setColor] = useState<ColorValue>("#3b82f6");
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const { data: session } = authClient.useSession();
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!(session?.$id && title.trim())) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCreating(true);
|
||||
try {
|
||||
await createSpace({
|
||||
title: title.trim(),
|
||||
color,
|
||||
userId: session.$id,
|
||||
});
|
||||
|
||||
// Reset form
|
||||
setTitle("");
|
||||
setColor("#3b82f6");
|
||||
setOpen(false);
|
||||
|
||||
// Notify parent to refresh
|
||||
onSpaceCreated?.();
|
||||
} catch {
|
||||
// TODO: Add proper error handling/toast notification
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const defaultTrigger = (
|
||||
<Button className="gap-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
New Space
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger>{triggerButton || defaultTrigger}</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New Space</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create a new visual workspace to organize your thoughts and ideas.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6 py-4">
|
||||
{/* Title Input */}
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
className="font-medium text-foreground text-sm"
|
||||
htmlFor="space-title"
|
||||
>
|
||||
Title
|
||||
</label>
|
||||
<Input
|
||||
id="space-title"
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && title.trim()) {
|
||||
handleCreate();
|
||||
}
|
||||
}}
|
||||
placeholder="Enter space title..."
|
||||
value={title}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div className="flex flex-col space-y-1">
|
||||
<span className="font-medium text-foreground text-sm">Color</span>
|
||||
<ColorPicker name="space-color" onChange={setColor} value={color} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="gap-2">
|
||||
<DialogClose>
|
||||
<Button
|
||||
disabled={isCreating}
|
||||
onClick={() => setOpen(false)}
|
||||
variant="outline"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose>
|
||||
<Button
|
||||
disabled={!title.trim() || isCreating}
|
||||
onClick={handleCreate}
|
||||
>
|
||||
{isCreating ? "Creating..." : "Create Space"}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { MoreHorizontal, Plus, Search } from "lucide-react";
|
||||
import { Tldraw } from "tldraw";
|
||||
import { NewSpaceDialog } from "@/components/new-space-dialog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -32,7 +33,7 @@ export function SpacesGrid() {
|
||||
return [] as SpaceCard[];
|
||||
}
|
||||
const rows = await listUserSpaceSnapshots(session.$id);
|
||||
return rows.map(({ row, snapshot }, idx) => {
|
||||
return rows.map(({ row, snapshot }) => {
|
||||
const snapshotText = snapshot
|
||||
? JSON.stringify(snapshot)
|
||||
: (row.snapshot ?? "");
|
||||
@@ -50,24 +51,16 @@ export function SpacesGrid() {
|
||||
return 0;
|
||||
}
|
||||
})();
|
||||
const COLORS = [
|
||||
"bg-blue-500",
|
||||
"bg-green-500",
|
||||
"bg-purple-500",
|
||||
"bg-orange-500",
|
||||
"bg-pink-500",
|
||||
"bg-cyan-500",
|
||||
] as const;
|
||||
return {
|
||||
id: row.spaceId,
|
||||
name: row.spaceId,
|
||||
name: row.title || row.spaceId,
|
||||
lastEdited: row.$updatedAt
|
||||
? new Date(row.$updatedAt).toLocaleString()
|
||||
: "",
|
||||
snapshotText,
|
||||
itemCount,
|
||||
// keep some color variety
|
||||
color: COLORS[idx % COLORS.length],
|
||||
// Use actual color from DB or fallback to color variety
|
||||
color: row.color || "#3b82f6", // Default to blue if no color
|
||||
} satisfies SpaceCard;
|
||||
});
|
||||
},
|
||||
@@ -78,6 +71,11 @@ export function SpacesGrid() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-6">
|
||||
<style>
|
||||
{`.tl-watermark_SEE-LICENSE {
|
||||
z-index: 1 !important;
|
||||
}`}
|
||||
</style>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -86,10 +84,7 @@ export function SpacesGrid() {
|
||||
Organize your thoughts and ideas in visual workspaces
|
||||
</p>
|
||||
</div>
|
||||
<Button className="gap-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
New Space
|
||||
</Button>
|
||||
<NewSpaceDialog onSpaceCreated={() => spacesQuery.refetch()} />
|
||||
</div>
|
||||
|
||||
{/* Search and Filters */}
|
||||
@@ -116,7 +111,10 @@ export function SpacesGrid() {
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`h-3 w-3 rounded-full ${space.color}`} />
|
||||
<div
|
||||
className="h-3 w-3 rounded-full"
|
||||
style={{ backgroundColor: space.color }}
|
||||
/>
|
||||
<h3 className="font-semibold text-foreground transition-colors group-hover:text-primary">
|
||||
{space.name}
|
||||
</h3>
|
||||
@@ -160,7 +158,11 @@ export function SpacesGrid() {
|
||||
onMount={(editor) => {
|
||||
editor.updateInstanceState({ isReadonly: true });
|
||||
}}
|
||||
snapshot={JSON.parse(space.snapshotText).document}
|
||||
snapshot={
|
||||
space.snapshotText
|
||||
? JSON.parse(space.snapshotText).document
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -186,10 +188,15 @@ export function SpacesGrid() {
|
||||
Create your first space to start organizing your thoughts and ideas
|
||||
visually.
|
||||
</p>
|
||||
<Button>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create Your First Space
|
||||
</Button>
|
||||
<NewSpaceDialog
|
||||
onSpaceCreated={() => spacesQuery.refetch()}
|
||||
triggerButton={
|
||||
<Button>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create Your First Space
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
79
apps/web/src/components/ui/color-picker.tsx
Normal file
79
apps/web/src/components/ui/color-picker.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { useId, useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const PRESET_COLORS = [
|
||||
"#3b82f6", // blue-500
|
||||
"#22c55e", // green-500
|
||||
"#a855f7", // purple-500
|
||||
"#f59e0b", // amber-500
|
||||
"#ec4899", // pink-500
|
||||
"#06b6d4", // cyan-500
|
||||
"#ef4444", // red-500
|
||||
"#10b981", // emerald-500
|
||||
] as const;
|
||||
|
||||
export type ColorValue = (typeof PRESET_COLORS)[number];
|
||||
|
||||
export function ColorPicker({
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
colors = PRESET_COLORS,
|
||||
size = 24,
|
||||
name,
|
||||
}: {
|
||||
value?: string | null;
|
||||
onChange?: (v: ColorValue) => void;
|
||||
className?: string;
|
||||
colors?: readonly string[];
|
||||
size?: number;
|
||||
name?: string;
|
||||
}) {
|
||||
const groupId = useId();
|
||||
const [internal, setInternal] = useState<string | null>(
|
||||
value ?? colors[0] ?? null
|
||||
);
|
||||
const active = value ?? internal;
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-wrap gap-2", className)}>
|
||||
{colors.map((c) => {
|
||||
const isActive = c.toLowerCase() === (active ?? "").toLowerCase();
|
||||
const id = `${groupId}-${c}`;
|
||||
return (
|
||||
<label
|
||||
className="group inline-flex cursor-pointer items-center justify-center"
|
||||
htmlFor={id}
|
||||
key={c}
|
||||
>
|
||||
<input
|
||||
checked={isActive}
|
||||
className="sr-only"
|
||||
id={id}
|
||||
name={name ?? groupId}
|
||||
onChange={() => {
|
||||
setInternal(c);
|
||||
onChange?.(c as ColorValue);
|
||||
}}
|
||||
type="radio"
|
||||
value={c}
|
||||
/>
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
"inline-flex rounded-full border border-border/40 ring-offset-background transition-shadow focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
isActive ? "ring-2 ring-ring ring-offset-2" : ""
|
||||
)}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
backgroundColor: c,
|
||||
}}
|
||||
/>
|
||||
<span className="sr-only">{c}</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
143
apps/web/src/components/ui/dialog.tsx
Normal file
143
apps/web/src/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import { Dialog as BaseDialog } from "@base-ui-components/react";
|
||||
import { XIcon } from "lucide-react";
|
||||
import type * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Dialog({ ...props }: React.ComponentProps<typeof BaseDialog.Root>) {
|
||||
return <BaseDialog.Root data-slot="dialog" {...props} />;
|
||||
}
|
||||
|
||||
function DialogPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof BaseDialog.Portal>) {
|
||||
return <BaseDialog.Portal data-slot="dialog-portal" {...props} />;
|
||||
}
|
||||
|
||||
function DialogTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof BaseDialog.Trigger>) {
|
||||
return <BaseDialog.Trigger data-slot="dialog-trigger" {...props} />;
|
||||
}
|
||||
|
||||
function DialogClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof BaseDialog.Close>) {
|
||||
return <BaseDialog.Close data-slot="dialog-close" {...props} />;
|
||||
}
|
||||
|
||||
function DialogOverlay({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof BaseDialog.Backdrop>) {
|
||||
return (
|
||||
<BaseDialog.Backdrop
|
||||
className={cn(
|
||||
"fixed inset-0 bg-black/50 transition-all duration-200 [&[data-ending-style]]:opacity-0 [&[data-starting-style]]:opacity-0",
|
||||
className
|
||||
)}
|
||||
data-slot="dialog-overlay"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogContent({
|
||||
className,
|
||||
children,
|
||||
showCloseButton = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof BaseDialog.Popup> & {
|
||||
showCloseButton?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<DialogPortal data-slot="dialog-portal">
|
||||
<DialogOverlay />
|
||||
<BaseDialog.Popup
|
||||
className={cn(
|
||||
"fixed z-50 grid w-full bg-popover text-popover-foreground sm:max-w-[calc(100%-2rem)]",
|
||||
"gap-4 rounded-lg border p-6 shadow-lg outline-none duration-200 sm:max-w-lg sm:scale-[calc(1-0.1*var(--nested-dialogs))]",
|
||||
"fixed bottom-0 w-full sm:top-[50%] sm:bottom-auto sm:left-[50%] sm:translate-x-[-50%] sm:translate-y-[-50%]",
|
||||
"duration-200",
|
||||
"data-[starting-style]:translate-y-full data-[starting-style]:opacity-0",
|
||||
"data-[ending-style]:translate-y-full data-[ending-style]:opacity-0",
|
||||
"data-[starting-style]:sm:translate-y-[-50%] data-[starting-style]:sm:scale-95",
|
||||
"data-[ending-style]:sm:translate-y-[-50%] data-[ending-style]:sm:scale-95",
|
||||
className
|
||||
)}
|
||||
data-slot="dialog-content"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{showCloseButton && (
|
||||
<DialogClose className="absolute top-4 right-4 rounded-xs text-muted-foreground opacity-50 ring-offset-popover transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-[3px] focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0">
|
||||
<XIcon />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
)}
|
||||
</BaseDialog.Popup>
|
||||
</DialogPortal>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||
data-slot="dialog-header"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||
className
|
||||
)}
|
||||
data-slot="dialog-footer"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof BaseDialog.Title>) {
|
||||
return (
|
||||
<BaseDialog.Title
|
||||
className={cn("font-semibold text-lg leading-none", className)}
|
||||
data-slot="dialog-title"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof BaseDialog.Description>) {
|
||||
return (
|
||||
<BaseDialog.Description
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
data-slot="dialog-description"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogClose,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
};
|
||||
@@ -122,6 +122,10 @@ body {
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
button {
|
||||
@apply transition-colors disabled:opacity-50 disabled:pointer-events-none cursor-pointer;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
|
||||
@@ -17,6 +17,9 @@ export type SpaceSnapshotRow = {
|
||||
spaceId: string;
|
||||
userId: string;
|
||||
snapshot: string; // JSON string containing { schema, document }
|
||||
// Optional metadata (create attributes in Appwrite collection)
|
||||
title?: string;
|
||||
color?: string; // hex color like #7c3aed
|
||||
$createdAt?: string;
|
||||
$updatedAt?: string;
|
||||
};
|
||||
@@ -123,6 +126,48 @@ export async function upsertSpaceSnapshot(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new space with optional initial snapshot and metadata (title, color).
|
||||
* Returns the new spaceId.
|
||||
*/
|
||||
export async function createSpace(args: {
|
||||
title: string;
|
||||
color: string; // hex
|
||||
snapshot?: RemoteSnapshot;
|
||||
userId: string;
|
||||
}): Promise<string> {
|
||||
ensureEnv();
|
||||
const uid = args.userId ?? (await getCurrentUserId());
|
||||
if (!uid) {
|
||||
throw new Error("Not authenticated");
|
||||
}
|
||||
|
||||
const data: Record<string, unknown> = {
|
||||
spaceId: crypto.randomUUID(),
|
||||
userId: uid,
|
||||
snapshot: args.snapshot ? JSON.stringify(args.snapshot) : undefined,
|
||||
title: args.title,
|
||||
color: args.color,
|
||||
};
|
||||
|
||||
// Limit access to the owner by default
|
||||
const permissions = [
|
||||
Permission.read(Role.user(uid)),
|
||||
Permission.update(Role.user(uid)),
|
||||
Permission.delete(Role.user(uid)),
|
||||
];
|
||||
|
||||
const doc = await databases.createDocument(
|
||||
DATABASE_ID,
|
||||
COLLECTION_ID,
|
||||
ID.unique(),
|
||||
data,
|
||||
permissions
|
||||
);
|
||||
|
||||
return (doc as Models.Document).spaceId as string;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all space snapshots for a user.
|
||||
*/
|
||||
|
||||
59
pnpm-lock.yaml
generated
59
pnpm-lock.yaml
generated
@@ -72,6 +72,9 @@ importers:
|
||||
|
||||
apps/web:
|
||||
dependencies:
|
||||
'@base-ui-components/react':
|
||||
specifier: 1.0.0-beta.3
|
||||
version: 1.0.0-beta.3(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
'@hookform/resolvers':
|
||||
specifier: ^5.1.1
|
||||
version: 5.2.1(react-hook-form@7.62.0(react@19.1.1))
|
||||
@@ -725,6 +728,27 @@ packages:
|
||||
resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@base-ui-components/react@1.0.0-beta.3':
|
||||
resolution: {integrity: sha512-4sAq6zmDA9ixV2HRjjeM1+tSEw5R6nvGjXUQmFoQnC3DZLEUdwO94gWDmUDdpoDuChn27jdbaJs9F0Ih4w2UAA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@types/react': ^17 || ^18 || ^19
|
||||
react: ^17 || ^18 || ^19
|
||||
react-dom: ^17 || ^18 || ^19
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@base-ui-components/utils@0.1.1':
|
||||
resolution: {integrity: sha512-HWXZA8upEKgrdL1rQqxWu1H+2tB2cXzY2jCxvgnpUv3eoWN2jldhXxMZnXIjZF7jahGxSWXfSIM/qskiTWFFxA==}
|
||||
peerDependencies:
|
||||
'@types/react': ^17 || ^18 || ^19
|
||||
react: ^17 || ^18 || ^19
|
||||
react-dom: ^17 || ^18 || ^19
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@biomejs/biome@2.2.2':
|
||||
resolution: {integrity: sha512-j1omAiQWCkhuLgwpMKisNKnsM6W8Xtt1l0WZmqY/dFj8QPNkIoTvk4tSsi40FaAAkBE1PU0AFG2RWFBWenAn+w==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
@@ -4217,6 +4241,9 @@ packages:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
reselect@5.1.1:
|
||||
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
|
||||
|
||||
resolve-pkg-maps@1.0.0:
|
||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||
|
||||
@@ -4454,6 +4481,9 @@ packages:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
tabbable@6.2.0:
|
||||
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
||||
|
||||
tailwind-merge@3.3.1:
|
||||
resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
|
||||
|
||||
@@ -5640,6 +5670,31 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
|
||||
'@base-ui-components/react@1.0.0-beta.3(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
'@base-ui-components/utils': 0.1.1(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
'@floating-ui/react-dom': 2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
'@floating-ui/utils': 0.2.10
|
||||
react: 19.1.1
|
||||
react-dom: 19.1.1(react@19.1.1)
|
||||
reselect: 5.1.1
|
||||
tabbable: 6.2.0
|
||||
use-sync-external-store: 1.5.0(react@19.1.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.12
|
||||
|
||||
'@base-ui-components/utils@0.1.1(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
'@floating-ui/utils': 0.2.10
|
||||
react: 19.1.1
|
||||
react-dom: 19.1.1(react@19.1.1)
|
||||
reselect: 5.1.1
|
||||
use-sync-external-store: 1.5.0(react@19.1.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.12
|
||||
|
||||
'@biomejs/biome@2.2.2':
|
||||
optionalDependencies:
|
||||
'@biomejs/cli-darwin-arm64': 2.2.2
|
||||
@@ -9025,6 +9080,8 @@ snapshots:
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
reselect@5.1.1: {}
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
|
||||
resolve@1.22.10:
|
||||
@@ -9357,6 +9414,8 @@ snapshots:
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
tabbable@6.2.0: {}
|
||||
|
||||
tailwind-merge@3.3.1: {}
|
||||
|
||||
tailwindcss@4.1.12: {}
|
||||
|
||||
Reference in New Issue
Block a user