mirror of
https://github.com/supabase/supabase.git
synced 2026-05-06 08:56:46 -04:00
chore(studio): remove @headlessui/react dependency (#44845)
Remove `@headlessui/react` as a direct dependency from both `apps/studio` and `packages/ui`. It's incompatible with React 19 (at the pinned v1 version) and overlaps with our existing Radix/shadcn primitives. The only usage was the `<Transition>` component in 3 files + a dead `Overlay` component in `packages/ui`. **Removed:** - `@headlessui/react` from `apps/studio/package.json` and `packages/ui/package.json` - Dead `packages/ui/src/lib/Overlay/` directory (not exported or imported anywhere) **Changed:** - `ChooseFunctionForm.tsx` — replaced `Transition` with a shadcn `Accordion` for the "View definition" toggle - `FileExplorerColumn.tsx` — replaced `Transition` with `framer-motion` `AnimatePresence` for drag-over overlay - `PreviewPane.tsx` — removed `Transition` wrapper entirely (wasn't visually animating on prod), replaced with simple conditional render Note: `@headlessui/react` will remain in `pnpm-lock.yaml` as a transitive dependency of `@graphiql/react` and `@graphiql/plugin-doc-explorer` — that's expected and not something we control. ## To test - **Triggers page** (`/dashboard/project/_/database/triggers`): Create or edit a trigger, click "Choose a function" to open the side panel. Click "View definition" on a function row — the SQL definition should expand/collapse with a smooth height animation. Clicking the row itself should still select the function. - **Storage explorer** (`/dashboard/project/_/storage/buckets/<bucket>`): Navigate into a folder, drag a file over the column — the drag overlay should fade in/out smoothly. - **Storage file preview** (`/dashboard/project/_/storage/buckets/<bucket>`): Click on a file — the preview pane should appear on the right (no animation, same as current prod behaviour). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Replaced several transition wrappers with new animation/mounting behavior for overlays, preview panes, and drag-over UI to improve consistency and responsiveness. * Swapped the function-definition toggle for an Accordion and updated click handling to prevent accidental row selection. * Removed the legacy overlay component, its context, and associated overlay styling. * **Chores** * Removed HeadlessUI dependency from project packages. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
import { Transition } from '@headlessui/react'
|
||||
import { useParams } from 'common'
|
||||
import { map as lodashMap, uniqBy } from 'lodash'
|
||||
import { ChevronDown, HelpCircle, Terminal } from 'lucide-react'
|
||||
import { HelpCircle, Terminal } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
import { Button, SidePanel } from 'ui'
|
||||
import {
|
||||
Accordion_Shadcn_,
|
||||
AccordionContent_Shadcn_,
|
||||
AccordionItem_Shadcn_,
|
||||
AccordionTrigger_Shadcn_,
|
||||
Button,
|
||||
SidePanel,
|
||||
} from 'ui'
|
||||
|
||||
import ProductEmptyState from '@/components/to-be-cleaned/ProductEmptyState'
|
||||
import InformationBox from '@/components/ui/InformationBox'
|
||||
@@ -145,40 +150,31 @@ export interface FunctionProps {
|
||||
}
|
||||
|
||||
const Function = ({ id, completeStatement, name, onClick }: FunctionProps) => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
return (
|
||||
<div className="cursor-pointer rounded p-3 px-6 hover:bg-studio" onClick={() => onClick(id)}>
|
||||
<div className="flex items-center justify-between space-x-3">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex items-center justify-center rounded bg-foreground p-1 text-background">
|
||||
<Terminal strokeWidth={2} size={14} />
|
||||
<Accordion_Shadcn_ type="single" collapsible>
|
||||
<AccordionItem_Shadcn_ value="definition" className="border-none">
|
||||
<div className="flex items-center justify-between space-x-3">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex items-center justify-center rounded bg-foreground p-1 text-background">
|
||||
<Terminal strokeWidth={2} size={14} />
|
||||
</div>
|
||||
<p className="mb-0 text-sm">{name}</p>
|
||||
</div>
|
||||
<AccordionTrigger_Shadcn_
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="py-0 text-xs font-normal text-foreground-light hover:no-underline"
|
||||
>
|
||||
View definition
|
||||
</AccordionTrigger_Shadcn_>
|
||||
</div>
|
||||
<p className="mb-0 text-sm">{name}</p>
|
||||
</div>
|
||||
<Button
|
||||
type="text"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setVisible(!visible)
|
||||
}}
|
||||
icon={<ChevronDown className={visible ? 'rotate-180 transform' : 'rotate-0 transform'} />}
|
||||
>
|
||||
{visible ? 'Hide definition' : 'View definition'}
|
||||
</Button>
|
||||
</div>
|
||||
<Transition
|
||||
show={visible}
|
||||
enter="transition ease-out duration-300"
|
||||
enterFrom="transform opacity-0"
|
||||
enterTo="transform opacity-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100"
|
||||
leaveTo="transform opacity-0"
|
||||
>
|
||||
<div className="mt-4 h-64 border border-default">
|
||||
<SqlEditor defaultValue={completeStatement} readOnly={true} contextmenu={false} />
|
||||
</div>
|
||||
</Transition>
|
||||
<AccordionContent_Shadcn_ className="[&>div]:pb-0" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="mt-4 h-64 border border-default">
|
||||
<SqlEditor defaultValue={completeStatement} readOnly={true} contextmenu={false} />
|
||||
</div>
|
||||
</AccordionContent_Shadcn_>
|
||||
</AccordionItem_Shadcn_>
|
||||
</Accordion_Shadcn_>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Transition } from '@headlessui/react'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import { get, noop, sum, uniqBy } from 'lodash'
|
||||
import { ChevronsDown, ChevronsUp, Copy, Eye, FolderPlus, Upload } from 'lucide-react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
@@ -37,35 +37,36 @@ import { useStorageExplorerStateSnapshot } from '@/state/storage-explorer'
|
||||
|
||||
const DragOverOverlay = ({ isOpen, onDragLeave, onDrop, folderIsEmpty }: any) => {
|
||||
return (
|
||||
<Transition
|
||||
show={isOpen}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0"
|
||||
enterTo="transform opacity-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100"
|
||||
leaveTo="transform opacity-0"
|
||||
className="h-full w-full absolute top-0"
|
||||
>
|
||||
<div
|
||||
onDragLeave={onDragLeave}
|
||||
onDrop={onDrop}
|
||||
className="absolute top-0 flex h-full w-full items-center justify-center"
|
||||
style={{ backgroundColor: folderIsEmpty ? 'rgba(0,0,0,0.1)' : 'rgba(0,0,0,0.2)' }}
|
||||
>
|
||||
{!folderIsEmpty && (
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.1, ease: 'easeOut' }}
|
||||
className="h-full w-full absolute top-0"
|
||||
>
|
||||
<div
|
||||
className="w-3/4 h-32 border-2 border-dashed border-muted rounded-md flex flex-col items-center justify-center p-6 pointer-events-none"
|
||||
style={{ backgroundColor: 'rgba(0,0,0,0.4)' }}
|
||||
onDragLeave={onDragLeave}
|
||||
onDrop={onDrop}
|
||||
className="absolute top-0 flex h-full w-full items-center justify-center"
|
||||
style={{ backgroundColor: folderIsEmpty ? 'rgba(0,0,0,0.1)' : 'rgba(0,0,0,0.2)' }}
|
||||
>
|
||||
<Upload className="text-white pointer-events-none" size={20} strokeWidth={2} />
|
||||
<p className="text-center text-sm text-white mt-2 pointer-events-none">
|
||||
Drop your files to upload to this folder
|
||||
</p>
|
||||
{!folderIsEmpty && (
|
||||
<div
|
||||
className="w-3/4 h-32 border-2 border-dashed border-muted rounded-md flex flex-col items-center justify-center p-6 pointer-events-none"
|
||||
style={{ backgroundColor: 'rgba(0,0,0,0.4)' }}
|
||||
>
|
||||
<Upload className="text-white pointer-events-none" size={20} strokeWidth={2} />
|
||||
<p className="text-center text-sm text-white mt-2 pointer-events-none">
|
||||
Drop your files to upload to this folder
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Transition>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { Transition } from '@headlessui/react'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { AlertCircle, ChevronDown, Copy, Download, LoaderCircle, Trash2, X } from 'lucide-react'
|
||||
import SVG from 'react-inlinesvg'
|
||||
import {
|
||||
@@ -129,154 +127,143 @@ export const PreviewPane = () => {
|
||||
if (!file) return null
|
||||
|
||||
const width = 450
|
||||
const isOpen = !isEmpty(file)
|
||||
const size = file.metadata ? formatBytes(file.metadata.size) : null
|
||||
const mimeType = file.metadata ? file.metadata.mimetype : undefined
|
||||
const createdAt = file.created_at ? new Date(file.created_at).toLocaleString() : 'Unknown'
|
||||
const updatedAt = file.updated_at ? new Date(file.updated_at).toLocaleString() : 'Unknown'
|
||||
|
||||
return (
|
||||
<Transition
|
||||
show={isOpen}
|
||||
enter="transition ease-out duration-150"
|
||||
enterFrom="transform opacity-0"
|
||||
enterTo="transform opacity-100"
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="transform opacity-100"
|
||||
leaveTo="transform opacity-0"
|
||||
<div
|
||||
className="h-full border-l border-overlay bg-surface-100 p-4 overflow-y-auto"
|
||||
style={{ width }}
|
||||
>
|
||||
<div
|
||||
className="h-full border-l border-overlay bg-surface-100 p-4 overflow-y-auto"
|
||||
style={{ width }}
|
||||
>
|
||||
{/* Preview Header */}
|
||||
<div className="flex w-full justify-end text-foreground-lighter transition-colors hover:text-foreground">
|
||||
<X
|
||||
className="cursor-pointer"
|
||||
size={14}
|
||||
strokeWidth={2}
|
||||
onClick={() => setSelectedFilePreview(undefined)}
|
||||
/>
|
||||
</div>
|
||||
{/* Preview Header */}
|
||||
<div className="flex w-full justify-end text-foreground-lighter transition-colors hover:text-foreground">
|
||||
<X
|
||||
className="cursor-pointer"
|
||||
size={14}
|
||||
strokeWidth={2}
|
||||
onClick={() => setSelectedFilePreview(undefined)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Preview Thumbnail*/}
|
||||
<div className="my-4 border border-overlay">
|
||||
<div className="flex h-56 w-full items-center 2xl:h-72">
|
||||
<PreviewFile item={file} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-6">
|
||||
{/* Preview Information */}
|
||||
<div className="space-y-1">
|
||||
<h5 className="break-words text-base text-foreground">{file.name}</h5>
|
||||
{file.isCorrupted && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<AlertCircle size={14} strokeWidth={2} className="text-foreground-light" />
|
||||
<p className="text-sm text-foreground-light">
|
||||
File is corrupted, please delete and reupload this file again
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{mimeType && (
|
||||
<p className="text-sm text-foreground-light">
|
||||
{mimeType}
|
||||
{size && <span> - {size}</span>}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Preview Metadata */}
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<label className="mb-1 text-xs text-foreground-lighter">Added on</label>
|
||||
<p className="text-sm text-foreground-light">{createdAt}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 text-xs text-foreground-lighter">Last modified</label>
|
||||
<p className="text-sm text-foreground-light">{updatedAt}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex space-x-2 border-b border-overlay pb-4">
|
||||
<Button
|
||||
type="default"
|
||||
icon={<Download />}
|
||||
disabled={file.isCorrupted}
|
||||
onClick={() => downloadFile(file)}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
{selectedBucket.public ? (
|
||||
<Button
|
||||
type="outline"
|
||||
icon={<Copy />}
|
||||
onClick={() => onCopyUrl(file.path!)}
|
||||
disabled={file.isCorrupted}
|
||||
>
|
||||
Get URL
|
||||
</Button>
|
||||
) : (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
type="outline"
|
||||
icon={<Copy />}
|
||||
iconRight={<ChevronDown />}
|
||||
disabled={file.isCorrupted}
|
||||
>
|
||||
Get URL
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="bottom" align="center">
|
||||
<DropdownMenuItem
|
||||
key="expires-one-week"
|
||||
onClick={() => onCopyUrl(file.path!, URL_EXPIRY_DURATION.WEEK)}
|
||||
>
|
||||
Expire in 1 week
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
key="expires-one-month"
|
||||
onClick={() => onCopyUrl(file.path!, URL_EXPIRY_DURATION.MONTH)}
|
||||
>
|
||||
Expire in 1 month
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
key="expires-one-year"
|
||||
onClick={() => onCopyUrl(file.path!, URL_EXPIRY_DURATION.YEAR)}
|
||||
>
|
||||
Expire in 1 year
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
key="custom-expiry"
|
||||
onClick={() => setSelectedFileCustomExpiry(file)}
|
||||
>
|
||||
Custom expiry
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
<ButtonTooltip
|
||||
type="outline"
|
||||
disabled={!canUpdateFiles}
|
||||
size="tiny"
|
||||
icon={<Trash2 strokeWidth={2} />}
|
||||
onClick={() => setSelectedItemsToDelete([file])}
|
||||
tooltip={{
|
||||
content: {
|
||||
side: 'bottom',
|
||||
text: !canUpdateFiles
|
||||
? 'You need additional permissions to delete this file'
|
||||
: undefined,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Delete file
|
||||
</ButtonTooltip>
|
||||
{/* Preview Thumbnail*/}
|
||||
<div className="my-4 border border-overlay">
|
||||
<div className="flex h-56 w-full items-center 2xl:h-72">
|
||||
<PreviewFile item={file} />
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<div className="w-full space-y-6">
|
||||
{/* Preview Information */}
|
||||
<div className="space-y-1">
|
||||
<h5 className="break-words text-base text-foreground">{file.name}</h5>
|
||||
{file.isCorrupted && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<AlertCircle size={14} strokeWidth={2} className="text-foreground-light" />
|
||||
<p className="text-sm text-foreground-light">
|
||||
File is corrupted, please delete and reupload this file again
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{mimeType && (
|
||||
<p className="text-sm text-foreground-light">
|
||||
{mimeType}
|
||||
{size && <span> - {size}</span>}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Preview Metadata */}
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<label className="mb-1 text-xs text-foreground-lighter">Added on</label>
|
||||
<p className="text-sm text-foreground-light">{createdAt}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 text-xs text-foreground-lighter">Last modified</label>
|
||||
<p className="text-sm text-foreground-light">{updatedAt}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex space-x-2 border-b border-overlay pb-4">
|
||||
<Button
|
||||
type="default"
|
||||
icon={<Download />}
|
||||
disabled={file.isCorrupted}
|
||||
onClick={() => downloadFile(file)}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
{selectedBucket.public ? (
|
||||
<Button
|
||||
type="outline"
|
||||
icon={<Copy />}
|
||||
onClick={() => onCopyUrl(file.path!)}
|
||||
disabled={file.isCorrupted}
|
||||
>
|
||||
Get URL
|
||||
</Button>
|
||||
) : (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
type="outline"
|
||||
icon={<Copy />}
|
||||
iconRight={<ChevronDown />}
|
||||
disabled={file.isCorrupted}
|
||||
>
|
||||
Get URL
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="bottom" align="center">
|
||||
<DropdownMenuItem
|
||||
key="expires-one-week"
|
||||
onClick={() => onCopyUrl(file.path!, URL_EXPIRY_DURATION.WEEK)}
|
||||
>
|
||||
Expire in 1 week
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
key="expires-one-month"
|
||||
onClick={() => onCopyUrl(file.path!, URL_EXPIRY_DURATION.MONTH)}
|
||||
>
|
||||
Expire in 1 month
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
key="expires-one-year"
|
||||
onClick={() => onCopyUrl(file.path!, URL_EXPIRY_DURATION.YEAR)}
|
||||
>
|
||||
Expire in 1 year
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
key="custom-expiry"
|
||||
onClick={() => setSelectedFileCustomExpiry(file)}
|
||||
>
|
||||
Custom expiry
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
<ButtonTooltip
|
||||
type="outline"
|
||||
disabled={!canUpdateFiles}
|
||||
size="tiny"
|
||||
icon={<Trash2 strokeWidth={2} />}
|
||||
onClick={() => setSelectedItemsToDelete([file])}
|
||||
tooltip={{
|
||||
content: {
|
||||
side: 'bottom',
|
||||
text: !canUpdateFiles
|
||||
? 'You need additional permissions to delete this file'
|
||||
: undefined,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Delete file
|
||||
</ButtonTooltip>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
"@graphiql/react": "^0.19.4",
|
||||
"@graphiql/toolkit": "^0.9.1",
|
||||
"@hcaptcha/react-hcaptcha": "^1.12.0",
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@heroicons/react": "^2.1.3",
|
||||
"@hookform/resolvers": "^3.1.1",
|
||||
"@mjackson/multipart-parser": "^0.10.1",
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"test:report": "open coverage/lcov-report/index.html"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||
"@radix-ui/react-aspect-ratio": "^1.0.3",
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
Overlay animations imported first as so placement styles are not affected.
|
||||
*/
|
||||
|
||||
.sbui-overlay--enter {
|
||||
@apply transition ease-out duration-100;
|
||||
}
|
||||
.sbui-overlay--enterFrom {
|
||||
@apply transform opacity-0 scale-95;
|
||||
}
|
||||
.sbui-overlay--enterTo {
|
||||
@apply transform opacity-100 scale-100;
|
||||
}
|
||||
.sbui-overlay--leave {
|
||||
@apply transition ease-in duration-75;
|
||||
}
|
||||
.sbui-overlay--leaveFrom {
|
||||
@apply transform opacity-100 scale-100;
|
||||
}
|
||||
.sbui-overlay--leaveTo {
|
||||
@apply transform opacity-0 scale-95;
|
||||
}
|
||||
|
||||
.sbui-overlay {
|
||||
@apply relative inline-block;
|
||||
}
|
||||
|
||||
.sbui-overlay-container {
|
||||
@apply w-56;
|
||||
}
|
||||
|
||||
.sbui-overlay-container--bottomRight {
|
||||
@apply absolute origin-top-right right-0 mt-2;
|
||||
}
|
||||
|
||||
.sbui-overlay-container--bottomLeft {
|
||||
@apply absolute origin-top-left left-0 mt-2;
|
||||
}
|
||||
|
||||
.sbui-overlay-container--bottomCenter {
|
||||
margin-top: 0.5rem;
|
||||
position: absolute;
|
||||
transform-origin: center;
|
||||
transform-origin: bottom center;
|
||||
left: 50%;
|
||||
-webkit-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.sbui-overlay-container--topRight {
|
||||
@apply origin-bottom-right;
|
||||
margin-top: -0.5rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
transform-origin: top left;
|
||||
transform: translatey(-100%);
|
||||
}
|
||||
|
||||
.sbui-overlay-container--topLeft {
|
||||
@apply origin-bottom-left;
|
||||
margin-top: -0.5rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
transform-origin: top left;
|
||||
transform: translatey(-100%);
|
||||
}
|
||||
|
||||
.sbui-overlay-container--topCenter {
|
||||
@apply origin-bottom-left;
|
||||
margin-top: -0.5rem;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform-origin: top left;
|
||||
transform: translatey(-100%) translateX(-50%);
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { Transition } from '@headlessui/react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { AnimationTailwindClasses } from '../../types'
|
||||
//@ts-ignore
|
||||
import { useOnClickOutside } from './../../lib/Hooks'
|
||||
// @ts-ignore
|
||||
import OverlayStyles from './Overlay.module.css'
|
||||
import { DropdownContext } from './OverlayContext'
|
||||
|
||||
interface Props {
|
||||
visible?: boolean
|
||||
overlay?: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
placement?: 'bottomLeft' | 'bottomRight' | 'bottomCenter' | 'topLeft' | 'topRight' | 'topCenter'
|
||||
onVisibleChange?: any
|
||||
disabled?: boolean
|
||||
triggerElement?: any
|
||||
overlayStyle?: React.CSSProperties
|
||||
overlayClassName?: string
|
||||
transition?: AnimationTailwindClasses
|
||||
}
|
||||
|
||||
function Overlay({
|
||||
visible,
|
||||
overlay,
|
||||
children,
|
||||
placement = 'topCenter',
|
||||
onVisibleChange,
|
||||
disabled,
|
||||
triggerElement,
|
||||
overlayStyle,
|
||||
overlayClassName,
|
||||
transition,
|
||||
}: Props) {
|
||||
const ref = useRef(null)
|
||||
const [visibleState, setVisibleState] = useState(!!visible)
|
||||
|
||||
let classes = [
|
||||
OverlayStyles['sbui-overlay-container'],
|
||||
OverlayStyles[`sbui-overlay-container--${placement}`],
|
||||
]
|
||||
if (overlayClassName) classes.push(overlayClassName)
|
||||
|
||||
function onToggle() {
|
||||
setVisibleState(!visibleState)
|
||||
if (onVisibleChange) onVisibleChange(visibleState)
|
||||
}
|
||||
|
||||
// allow ovveride of Dropdown
|
||||
useEffect(() => {
|
||||
setVisibleState(!!visible)
|
||||
}, [visible])
|
||||
|
||||
// detect clicks from outside
|
||||
useOnClickOutside(ref, () => {
|
||||
if (visibleState) {
|
||||
setVisibleState(!visibleState)
|
||||
}
|
||||
})
|
||||
|
||||
const TriggerElement = () => {
|
||||
return <div onClick={onToggle}>{triggerElement}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ref} className={OverlayStyles['sbui-overlay']}>
|
||||
{placement === 'bottomRight' || placement === 'bottomLeft' || placement === 'bottomCenter' ? (
|
||||
<TriggerElement />
|
||||
) : null}
|
||||
<Transition
|
||||
show={visibleState}
|
||||
enter={OverlayStyles[`sbui-overlay--enter`]}
|
||||
enterFrom={OverlayStyles[`sbui-overlay--enterFrom`]}
|
||||
enterTo={OverlayStyles[`sbui-overlay--enterTo`]}
|
||||
leave={OverlayStyles[`sbui-overlay--leave`]}
|
||||
leaveFrom={OverlayStyles[`sbui-overlay--leaveFrom`]}
|
||||
leaveTo={OverlayStyles[`sbui-overlay--leaveTo`]}
|
||||
>
|
||||
<div className={classes.join(' ')} style={overlayStyle}>
|
||||
<DropdownContext.Provider value={{ onClick: onToggle }}>
|
||||
{children}
|
||||
</DropdownContext.Provider>
|
||||
</div>
|
||||
</Transition>
|
||||
{placement === 'topRight' || placement === 'topLeft' || placement === 'topCenter' ? (
|
||||
<TriggerElement />
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Overlay
|
||||
@@ -1,7 +0,0 @@
|
||||
import { createContext } from 'react'
|
||||
|
||||
// Make sure the shape of the default value passed to
|
||||
// createContext matches the shape that the consumers expect!
|
||||
export const DropdownContext = createContext({
|
||||
onClick: (e: any) => {},
|
||||
})
|
||||
Generated
-6
@@ -886,9 +886,6 @@ importers:
|
||||
'@hcaptcha/react-hcaptcha':
|
||||
specifier: ^1.12.0
|
||||
version: 1.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@headlessui/react':
|
||||
specifier: ^1.7.17
|
||||
version: 1.7.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@heroicons/react':
|
||||
specifier: ^2.1.3
|
||||
version: 2.1.3(react@18.3.1)
|
||||
@@ -2437,9 +2434,6 @@ importers:
|
||||
|
||||
packages/ui:
|
||||
dependencies:
|
||||
'@headlessui/react':
|
||||
specifier: ^1.7.17
|
||||
version: 1.7.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-accordion':
|
||||
specifier: ^1.2.12
|
||||
version: 1.2.12(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
||||
Reference in New Issue
Block a user