mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Chore/migrate modals 1 (#46216)
## Problem We still use the deprecated `Modal` component in many places. ## Solution Migrate to the new `Dialog` component. Removed the design system page that was missing anyway: https://supabase.com/design-system/docs/fragments/modal This PR focuses on the design system and docs related components. ## How to test - Docs videos: - prod: https://supabase.com/docs/guides/auth - staging: https://docs-git-chore-migrate-modals-1-supabase.vercel.app/docs/guides/auth - Feedback modal: not sure how to show it on staging but it should look identical - Privacy settings: not sure how to show it on staging but it should look identical - `PopupFrame`: this one is not used anywhere AFAIK. If confirmed I could delete it. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **Chores** * Removed Modal component documentation entries and example patterns from the design system. * Migrated internal modal and dialog implementations to use the Dialog component suite for standardized behavior and improved consistency across the application. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46216?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
57b7aa2f3d
commit
31c8601201
10 files changed
+201
-441
No files matched your search
@@ -132,11 +132,6 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/fragments/empty-state-presentational',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Modal',
|
||||
href: '/docs/fragments/modal',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Page Container',
|
||||
href: '/docs/fragments/page-container',
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Link2 } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Button, Modal } from 'ui'
|
||||
|
||||
export default function ModalVerticalAlignedFooter() {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button type="default" onClick={() => setVisible(!visible)}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(!visible)}
|
||||
onConfirm={() => setVisible(!visible)}
|
||||
title="This is the title of the modal"
|
||||
description="And i am the description"
|
||||
alignFooter="right"
|
||||
size="medium"
|
||||
hideClose={false}
|
||||
header={
|
||||
<div className="flex items-center gap-2 text-foreground">
|
||||
<div className="text-brand">
|
||||
<Link2 />
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<h3>This is the title</h3>
|
||||
<span className="text-xs text-foreground-muted">This is the title</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
Modal content is inserted here, if you need to insert anything into the Modal you can do
|
||||
so via `children`.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Link2 } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Badge, Button, Modal } from 'ui'
|
||||
|
||||
export default function ModalVerticalCustomFooter() {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button type="default" onClick={() => setVisible(!visible)}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(!visible)}
|
||||
onConfirm={() => setVisible(!visible)}
|
||||
title="This is the title of the modal"
|
||||
description="And i am the description"
|
||||
size="medium"
|
||||
hideClose={false}
|
||||
header={
|
||||
<div className="flex items-center gap-2 text-foreground">
|
||||
<div className="text-brand">
|
||||
<Link2 />
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<h3>This is the title</h3>
|
||||
<span className="text-xs text-foreground-muted">This is the title</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
customFooter={
|
||||
<div className="flex items-center gap-3">
|
||||
<div>
|
||||
<Badge variant="destructive">Proceed with caution</Badge>
|
||||
</div>
|
||||
<Button type="secondary">Cancel</Button>
|
||||
<Button type="danger">Delete</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
Modal content is inserted here, if you need to insert anything into the Modal you can do
|
||||
so via `children`.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Link2 } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Button, Modal } from 'ui'
|
||||
|
||||
export default function ModalVerticalHideFooter() {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button type="default" onClick={() => setVisible(!visible)}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(!visible)}
|
||||
onConfirm={() => setVisible(!visible)}
|
||||
title="This is the title of the modal"
|
||||
description="And i am the description"
|
||||
hideFooter={true}
|
||||
size="medium"
|
||||
hideClose={false}
|
||||
header={
|
||||
<div className="flex items-center gap-2 text-foreground">
|
||||
<div className="text-brand">
|
||||
<Link2 />
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<h3>This is the title</h3>
|
||||
<span className="text-xs text-foreground-muted">This is the title</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
Modal content is inserted here, if you need to insert anything into the Modal you can do
|
||||
so via `children`.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Link2 } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Button, Modal } from 'ui'
|
||||
|
||||
export default function ModalVerticalLayout() {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button type="default" onClick={() => setVisible(!visible)}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(!visible)}
|
||||
onConfirm={() => setVisible(!visible)}
|
||||
title="This is the title of the modal"
|
||||
description="And i am the description"
|
||||
layout="vertical"
|
||||
size="medium"
|
||||
hideClose={false}
|
||||
header={
|
||||
<div className="flex items-center gap-2 text-foreground">
|
||||
<div className="text-brand">
|
||||
<Link2 />
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<h3>This is the title</h3>
|
||||
<span className="text-xs text-foreground-muted">This is the title</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
Modal content is inserted here, if you need to insert anything into the Modal you can do
|
||||
so via `children`.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,25 +1,24 @@
|
||||
'use client'
|
||||
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { IS_PLATFORM } from '~/lib/constants'
|
||||
import { useSendFeedbackMutation } from '~/lib/fetch/feedback'
|
||||
import { useSendTelemetryEvent } from '~/lib/telemetry'
|
||||
import { useConstant, useIsLoggedIn, type Database } from 'common'
|
||||
import { Check, MessageSquareQuote, X } from 'lucide-react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import {
|
||||
type CSSProperties,
|
||||
type MouseEventHandler,
|
||||
forwardRef,
|
||||
useReducer,
|
||||
useRef,
|
||||
useState,
|
||||
type CSSProperties,
|
||||
type MouseEventHandler,
|
||||
} from 'react'
|
||||
|
||||
import { type Database, useConstant, useIsLoggedIn } from 'common'
|
||||
import { Button, cn } from 'ui'
|
||||
|
||||
import { IS_PLATFORM } from '~/lib/constants'
|
||||
import { useSendFeedbackMutation } from '~/lib/fetch/feedback'
|
||||
import { useSendTelemetryEvent } from '~/lib/telemetry'
|
||||
import { getLinearTeam, getSanitizedTabParams } from './Feedback.utils'
|
||||
import { type FeedbackFields, FeedbackModal } from './FeedbackModal'
|
||||
import { FeedbackModal, type FeedbackFields } from './FeedbackModal'
|
||||
|
||||
const FeedbackButton = forwardRef<
|
||||
HTMLButtonElement,
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { SubmitHandler, useForm } from 'react-hook-form'
|
||||
import { Button, Form, FormControl, FormField, Input, Modal, Textarea } from 'ui'
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogSection,
|
||||
DialogSectionSeparator,
|
||||
DialogTitle,
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
Input,
|
||||
Textarea,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
import * as z from 'zod'
|
||||
|
||||
@@ -24,6 +38,7 @@ function FeedbackModal({ visible, page, onCancel, onSubmit }: FeedbackModalProps
|
||||
defaultValues: { page, title: '', comment: '' },
|
||||
resolver: zodResolver(formSchema),
|
||||
})
|
||||
const formId = 'feedback-form'
|
||||
const { reset } = form
|
||||
const { isSubmitting } = form.formState
|
||||
|
||||
@@ -38,75 +53,69 @@ function FeedbackModal({ visible, page, onCancel, onSubmit }: FeedbackModalProps
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
hideFooter
|
||||
header="Leave a comment"
|
||||
visible={visible}
|
||||
onCancel={handleCancel}
|
||||
onEscapeKeyDown={handleCancel}
|
||||
>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)}>
|
||||
<Modal.Content className="pt-4 pb-2 flex flex-col gap-2">
|
||||
<input type="hidden" id="page" {...form.register('page')} />
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout layout="vertical" label="Title">
|
||||
<FormControl className="col-span-6">
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="comment"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout layout="vertical" label="Comment" afterLabel="(not anonymous)">
|
||||
<FormControl className="col-span-6">
|
||||
<Textarea {...field} rows={4} className="resize-none" />
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
<div className="flex gap-2 text-xs text-foreground-light leading-relaxed">
|
||||
<span className="shrink-0 mt-0.5">💡</span>
|
||||
<div>
|
||||
<strong>Need help or support?</strong> This feedback form is for documentation
|
||||
improvements only. For technical support, please submit a{' '}
|
||||
<a
|
||||
href="https://supabase.com/dashboard/support/new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-brand-link hover:underline"
|
||||
>
|
||||
support request
|
||||
</a>
|
||||
.
|
||||
<Dialog open={visible} onOpenChange={() => onCancel()}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Leave a comment</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogSectionSeparator />
|
||||
<Form {...form}>
|
||||
<form id={formId} onSubmit={form.handleSubmit(handleSubmit)}>
|
||||
<DialogSection className="space-y-4">
|
||||
<input type="hidden" id="page" {...form.register('page')} />
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout layout="vertical" label="Title">
|
||||
<FormControl className="col-span-6">
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="comment"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout layout="vertical" label="Comment" afterLabel="(not anonymous)">
|
||||
<FormControl className="col-span-6">
|
||||
<Textarea {...field} rows={4} className="resize-none" />
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
<div className="flex gap-2 text-xs text-foreground-light leading-relaxed">
|
||||
<span className="shrink-0 mt-0.5">💡</span>
|
||||
<div>
|
||||
<strong>Need help or support?</strong> This feedback form is for documentation
|
||||
improvements only. For technical support, please submit a{' '}
|
||||
<a
|
||||
href="https://supabase.com/dashboard/support/new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-brand-link hover:underline"
|
||||
>
|
||||
support request
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="pt-2 pb-4">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button
|
||||
htmlType="reset"
|
||||
type="default"
|
||||
onClick={handleCancel}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" loading={isSubmitting} disabled={isSubmitting}>
|
||||
Submit feedback
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</form>
|
||||
</Form>
|
||||
</Modal>
|
||||
</DialogSection>
|
||||
</form>
|
||||
</Form>
|
||||
<DialogFooter>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button htmlType="reset" type="default" onClick={handleCancel} disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" form={formId} loading={isSubmitting} disabled={isSubmitting}>
|
||||
Submit feedback
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useBreakpoint } from 'common'
|
||||
import { Play } from 'lucide-react'
|
||||
import Image from 'next/image'
|
||||
import React, { ReactNode } from 'react'
|
||||
import { cn, Modal } from 'ui'
|
||||
import { Dialog, DialogContent, DialogTrigger } from 'ui'
|
||||
|
||||
interface ExpandableVideoProps {
|
||||
videoId: string
|
||||
@@ -28,22 +28,6 @@ export function ExpandableVideo({
|
||||
const [expandVideo, setExpandVideo] = React.useState(false)
|
||||
const isMobile = useBreakpoint(768)
|
||||
|
||||
React.useEffect(() => {
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
return setExpandVideo(false)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown)
|
||||
}
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isMobile) setExpandVideo(false)
|
||||
}, [isMobile])
|
||||
@@ -83,51 +67,38 @@ export function ExpandableVideo({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
visible={expandVideo}
|
||||
hideFooter
|
||||
className={cn(
|
||||
'bg-[#f8f9fa]/95! dark:bg-[#1c1c1c]/80!',
|
||||
'border-[#e6e8eb]/90! dark:border-[#282828]/90!',
|
||||
'transition ease-out',
|
||||
'mx-auto backdrop-blur-md w-[calc(100%-2rem)]'
|
||||
)}
|
||||
onInteractOutside={(e) => {
|
||||
// Only hide menu when clicking outside, not focusing outside
|
||||
// Prevents Firefox dropdown issue that immediately closes menu after opening
|
||||
if (e.type === 'dismissableLayer.pointerDownOutside') {
|
||||
setExpandVideo(!expandVideo)
|
||||
}
|
||||
}}
|
||||
size="xxlarge"
|
||||
>
|
||||
<div className="w-full! flex items-center justify-center">
|
||||
<div className="relative w-full">
|
||||
<button
|
||||
onClick={() => setExpandVideo(false)}
|
||||
className="text-foreground-light hover:text-foreground absolute -top-8 right-0"
|
||||
>
|
||||
<p className="text-xs">Close</p>
|
||||
</button>
|
||||
<div className="video-container rounded-lg! border-none! overflow-hidden!">
|
||||
<iframe
|
||||
src={`https://www.youtube-nocookie.com/embed/${videoId}`}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
<Dialog open={expandVideo} onOpenChange={(open) => setExpandVideo(open)}>
|
||||
<DialogTrigger asChild>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (onOpenCallback) onOpenCallback()
|
||||
setExpandVideo(true)
|
||||
}}
|
||||
className={['w-full', triggerContainerClassName].join(' ').trim()}
|
||||
>
|
||||
{trigger ?? <CliccablePreview />}
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="xxlarge">
|
||||
<div className="w-full! flex items-center justify-center">
|
||||
<div className="relative w-full">
|
||||
<button
|
||||
onClick={() => setExpandVideo(false)}
|
||||
className="text-foreground-light hover:text-foreground absolute -top-8 right-0"
|
||||
>
|
||||
<p className="text-xs">Close</p>
|
||||
</button>
|
||||
<div className="video-container rounded-lg! border-none! overflow-hidden!">
|
||||
<iframe
|
||||
src={`https://www.youtube-nocookie.com/embed/${videoId}`}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (onOpenCallback) onOpenCallback()
|
||||
setExpandVideo(true)
|
||||
}}
|
||||
className={['w-full', triggerContainerClassName].join(' ').trim()}
|
||||
>
|
||||
{trigger ?? <CliccablePreview />}
|
||||
</button>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useBreakpoint } from 'common'
|
||||
import React, { ReactNode } from 'react'
|
||||
import { cn, Modal } from 'ui'
|
||||
import { cn, Dialog, DialogContent, DialogTrigger } from 'ui'
|
||||
|
||||
interface PopupFrameProps {
|
||||
triggerContainerClassName?: string
|
||||
@@ -20,48 +20,24 @@ export function PopupFrame({
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const isMobile = useBreakpoint(768)
|
||||
|
||||
React.useEffect(() => {
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
return setOpen(false)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown)
|
||||
}
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isMobile) setOpen(false)
|
||||
}, [isMobile])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
visible={open}
|
||||
hideFooter
|
||||
showCloseButton={false}
|
||||
className={cn(
|
||||
'bg-[#f8f9fa]/95! dark:bg-[#1c1c1c]/80!',
|
||||
'border-[#e6e8eb]/90! dark:border-[#282828]/90!',
|
||||
'transition ease-out',
|
||||
'mx-auto backdrop-blur-md w-[calc(100%-2rem)]',
|
||||
className
|
||||
)}
|
||||
onInteractOutside={(e) => {
|
||||
// Only hide menu when clicking outside, not focusing outside
|
||||
// Prevents Firefox dropdown issue that immediately closes menu after opening
|
||||
if (e.type === 'dismissableLayer.pointerDownOutside') {
|
||||
setOpen(!open)
|
||||
}
|
||||
}}
|
||||
size="xxlarge"
|
||||
>
|
||||
<Dialog open={open} onOpenChange={(open) => setOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (onOpenCallback) onOpenCallback()
|
||||
setOpen(true)
|
||||
}}
|
||||
className={cn('w-full', triggerContainerClassName)}
|
||||
>
|
||||
{trigger ?? 'Expand'}
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className={className} size="xxlarge">
|
||||
<div className="device-frame w-full! h-full flex items-center justify-center">
|
||||
<div className="modal-group relative w-full h-full">
|
||||
<button
|
||||
@@ -75,16 +51,7 @@ export function PopupFrame({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (onOpenCallback) onOpenCallback()
|
||||
setOpen(true)
|
||||
}}
|
||||
className={cn('w-full', triggerContainerClassName)}
|
||||
>
|
||||
{trigger ?? 'Expand'}
|
||||
</button>
|
||||
</>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,19 @@
|
||||
import { useConsentState } from 'common'
|
||||
import Link from 'next/link'
|
||||
import { PropsWithChildren, useState } from 'react'
|
||||
import { Label, Modal, Switch } from 'ui'
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogSection,
|
||||
DialogSectionSeparator,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
Label,
|
||||
Switch,
|
||||
} from 'ui'
|
||||
|
||||
import { Admonition } from '../admonition'
|
||||
|
||||
@@ -38,36 +50,21 @@ export const PrivacySettings = ({
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button {...props} onClick={() => setIsOpen(true)}>
|
||||
{children}
|
||||
</button>
|
||||
|
||||
<Modal
|
||||
closable
|
||||
visible={isOpen}
|
||||
alignFooter="right"
|
||||
onCancel={handleCancel}
|
||||
onConfirm={handleConfirmPreferences}
|
||||
header="Privacy Settings"
|
||||
onInteractOutside={(e) => {
|
||||
// Only hide menu when clicking outside, not focusing outside
|
||||
// Prevents Firefox dropdown issue that immediately closes menu after opening
|
||||
if (e.type === 'dismissableLayer.pointerDownOutside') {
|
||||
setIsOpen(!isOpen)
|
||||
}
|
||||
}}
|
||||
className="max-w-[calc(100vw-4rem)]"
|
||||
size="medium"
|
||||
>
|
||||
<div className="divide-y divide-border">
|
||||
{categories === null ? (
|
||||
<Modal.Content>
|
||||
<Dialog open={isOpen} onOpenChange={(open) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
<button {...props} onClick={() => setIsOpen(true)}>
|
||||
{children}
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="medium">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Privacy Settings</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogSectionSeparator />
|
||||
<DialogSection className="space-y-4">
|
||||
<div className="divide-y divide-border">
|
||||
{categories === null ? (
|
||||
<Admonition
|
||||
type="warning"
|
||||
title="Unable to Load Privacy Settings"
|
||||
@@ -82,21 +79,25 @@ export const PrivacySettings = ({
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Modal.Content>
|
||||
) : (
|
||||
[...categories]
|
||||
.reverse()
|
||||
.map((category) => (
|
||||
<Category
|
||||
key={category.slug}
|
||||
category={category}
|
||||
handleServicesChange={handleServicesChange}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
) : (
|
||||
[...categories]
|
||||
.reverse()
|
||||
.map((category) => (
|
||||
<Category
|
||||
key={category.slug}
|
||||
category={category}
|
||||
handleServicesChange={handleServicesChange}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</DialogSection>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => setIsOpen(false)}>Cancel</Button>
|
||||
<Button onClick={handleConfirmPreferences}>Confirm</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -134,32 +135,30 @@ function Category({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal.Content key={category.slug}>
|
||||
<div className="flex flex-row items-center justify-between gap-4">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base" htmlFor={category.slug}>
|
||||
{category.label}
|
||||
</Label>
|
||||
<div className="text-sm text-foreground-light" id={`${category.slug}-description`}>
|
||||
{category.description}
|
||||
<br />
|
||||
<Link
|
||||
href="https://supabase.com/privacy#8-cookies-and-similar-technologies-used-on-our-european-services"
|
||||
className="underline"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-between gap-4" key={category.slug}>
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base" htmlFor={category.slug}>
|
||||
{category.label}
|
||||
</Label>
|
||||
<div className="text-sm text-foreground-light" id={`${category.slug}-description`}>
|
||||
{category.description}
|
||||
<br />
|
||||
<Link
|
||||
href="https://supabase.com/privacy#8-cookies-and-similar-technologies-used-on-our-european-services"
|
||||
className="underline"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</div>
|
||||
<Switch
|
||||
id={category.slug}
|
||||
checked={isChecked}
|
||||
disabled={category.isEssential}
|
||||
defaultChecked={isChecked}
|
||||
onCheckedChange={handleChange}
|
||||
aria-describedby={`${category.slug}-description`}
|
||||
/>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Switch
|
||||
id={category.slug}
|
||||
checked={isChecked}
|
||||
disabled={category.isEssential}
|
||||
defaultChecked={isChecked}
|
||||
onCheckedChange={handleChange}
|
||||
aria-describedby={`${category.slug}-description`}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user