mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 18:05:11 +03:00
151 files changed
+5830
-4401
No files matched your search
@@ -1,169 +0,0 @@
|
||||
import { render } from 'react-dom'
|
||||
import { IconCommand } from 'ui'
|
||||
import { createElement, FC, useEffect, useRef, Fragment } from 'react'
|
||||
import algoliasearch from 'algoliasearch/lite'
|
||||
import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'
|
||||
import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
// [Joshen] We're currently using DocSearch from Algolia as it provides a nice
|
||||
// UI out of the box + some good preconfigured search settings (e.g hierarchy).
|
||||
// However, we're using our own Algolia account to store the records in the indexes
|
||||
// (rather than going through the DocSearch program from Algolia where they'll crawl
|
||||
// our site for us). Refer to scripts/build-search on how we're saving the records.
|
||||
|
||||
// Using Algolia's autocomplete library gives us full flexbility in terms of customizing
|
||||
// our search experience, but that will take time to figure out. Hence why for now we're just
|
||||
// using DocSearch with our own records.
|
||||
|
||||
// Potentially for search, we could
|
||||
// - Go ahead with the DocSearch program and let them crawl our site to generate the records
|
||||
// - But we need to ensure that our site is semantically correct first
|
||||
// - Use Algolia itself to flesh out our own search logic
|
||||
// - The basics are already set up to be honest, but will take time to make it great
|
||||
// - Go back to Typesense if we deem that Algolia is not helpful in the long run
|
||||
|
||||
const searchClient = algoliasearch(
|
||||
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID,
|
||||
process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY
|
||||
)
|
||||
|
||||
// [Joshen] Not working properly, but lets not get stuck on this
|
||||
// Priority just to get a search working
|
||||
const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
|
||||
key: 'docs-search',
|
||||
limit: 3,
|
||||
//@ts-ignore
|
||||
transformSource({ source }) {
|
||||
return {
|
||||
...source,
|
||||
templates: {
|
||||
...source.templates,
|
||||
header({ state }) {
|
||||
if (state.query) return null
|
||||
return (
|
||||
<Fragment>
|
||||
<span className="aa-SourceHeaderTitle">Your searches</span>
|
||||
<div className="aa-SourceHeaderLine" />
|
||||
</Fragment>
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
interface Props {}
|
||||
|
||||
const AlgoliaSearch: FC<Props> = ({}) => {
|
||||
const searchRef = useRef(null)
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchRef.current) {
|
||||
return undefined
|
||||
}
|
||||
const search = autocomplete({
|
||||
openOnFocus: true,
|
||||
container: searchRef.current,
|
||||
defaultActiveItemId: 0,
|
||||
detachedMediaQuery: '',
|
||||
// @ts-ignore
|
||||
renderer: { createElement, Fragment, render },
|
||||
placeholder: 'Search docs',
|
||||
plugins: [recentSearchesPlugin],
|
||||
renderNoResults({ state, render }, root) {
|
||||
render(
|
||||
<div className="text-scale-1100 py-2 text-sm px-4">
|
||||
No results found for {state.query}.
|
||||
</div>,
|
||||
root
|
||||
)
|
||||
},
|
||||
navigator: {
|
||||
navigate({ itemUrl }) {
|
||||
router.push(itemUrl)
|
||||
},
|
||||
},
|
||||
onSelect({ item, setQuery, setIsOpen, refresh }) {
|
||||
//console.log('onSelect', item.url)
|
||||
},
|
||||
// @ts-ignore
|
||||
getSources({ query }) {
|
||||
return [
|
||||
{
|
||||
sourceId: 'pages',
|
||||
templates: {
|
||||
item({ item, components }) {
|
||||
return (
|
||||
<a
|
||||
href={item.url as string}
|
||||
className="aa-ItemLink truncate flex justify-between space-x-4"
|
||||
>
|
||||
<div className="aa-ItemContent w-full">
|
||||
<div className="aa-ItemTitle flex items-center space-x-1">
|
||||
{item.category && (
|
||||
<p
|
||||
className={`${
|
||||
['cli', 'api'].includes(item.category as string)
|
||||
? 'uppercase'
|
||||
: 'capitalize'
|
||||
}`}
|
||||
>
|
||||
<>
|
||||
{item.category}
|
||||
{item.version ? ` (${item.version})` : ''}:
|
||||
</>
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
<components.Highlight hit={item} attribute="title" />
|
||||
</p>
|
||||
</div>
|
||||
<p className="aa-ItemContentSubtitle">{item.description as string}</p>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
},
|
||||
},
|
||||
getItemUrl({ item }) {
|
||||
return item.url
|
||||
},
|
||||
getItems() {
|
||||
// if (!query) return []
|
||||
return getAlgoliaResults({
|
||||
searchClient,
|
||||
queries: [
|
||||
{
|
||||
indexName: 'dev_docs',
|
||||
query,
|
||||
},
|
||||
],
|
||||
})
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
})
|
||||
|
||||
return () => {
|
||||
search.destroy()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="w-[200px] relative">
|
||||
<div ref={searchRef} />
|
||||
<div className="flex items-center space-x-1 absolute top-[7px] right-2">
|
||||
<div className="text-scale-1200 flex items-center justify-center h-6 w-6 rounded bg-scale-500">
|
||||
<IconCommand size={12} strokeWidth={1.5} />
|
||||
</div>
|
||||
<div className="text-xs text-scale-1200 flex items-center justify-center h-6 w-6 rounded bg-scale-500">
|
||||
K
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AlgoliaSearch
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useTheme } from 'common/Providers'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState, useEffect, FC } from 'react'
|
||||
import { IconMenu, IconMoon, IconSearch, IconSun, IconCommand, Listbox } from 'ui'
|
||||
import { useTheme } from 'common/Providers'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { IconCommand, IconMenu, IconMoon, IconSearch, IconSun, Listbox } from 'ui'
|
||||
import { REFERENCES } from './Navigation.constants'
|
||||
|
||||
import { getPageType } from '~/lib/helpers'
|
||||
|
||||
import SearchButton from '../Search/SearchButton'
|
||||
|
||||
const NavBar: FC = () => {
|
||||
@@ -36,14 +37,6 @@ const NavBar: FC = () => {
|
||||
{ text: 'Reference', key: 'reference', link: '/reference' },
|
||||
]
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString())
|
||||
toggleTheme()
|
||||
|
||||
const key = localStorage.getItem('supabaseDarkMode')
|
||||
document.documentElement.className = key === 'true' ? 'dark' : ''
|
||||
}
|
||||
|
||||
const onSelectVersion = (version: string) => {
|
||||
// [Joshen] Ideally we use <Link> but this works for now
|
||||
if (!library) return
|
||||
@@ -183,7 +176,7 @@ const NavBar: FC = () => {
|
||||
</a>
|
||||
</li>
|
||||
<li className="px-4">
|
||||
<div className="cursor-pointer" onClick={toggleDarkMode}>
|
||||
<div className="cursor-pointer" onClick={() => toggleTheme()}>
|
||||
{isDarkMode ? (
|
||||
<IconMoon size={18} strokeWidth={2} className="text-scale-1200" />
|
||||
) : (
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useTheme } from 'common/Providers'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { Button, IconCommand, IconMenu, IconMoon, IconSearch, IconSun, Input, Listbox } from 'ui'
|
||||
import { Button, IconCommand, IconMenu, IconMoon, IconSearch, IconSun, Listbox } from 'ui'
|
||||
import { REFERENCES } from '~/components/Navigation/Navigation.constants'
|
||||
import { useTheme } from 'common/Providers'
|
||||
|
||||
import { getPageType } from '~/lib/helpers'
|
||||
import SearchButton from '~/components/Search/SearchButton'
|
||||
|
||||
import SearchButton from './../../Search/SearchButton'
|
||||
|
||||
const TopNavBar: FC = () => {
|
||||
const { isDarkMode, toggleTheme } = useTheme()
|
||||
@@ -36,14 +37,6 @@ const TopNavBar: FC = () => {
|
||||
{ text: 'Reference', key: 'reference', link: '/reference' },
|
||||
]
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString())
|
||||
toggleTheme()
|
||||
|
||||
const key = localStorage.getItem('supabaseDarkMode')
|
||||
document.documentElement.className = key === 'true' ? 'dark' : ''
|
||||
}
|
||||
|
||||
const onSelectVersion = (version: string) => {
|
||||
// [Joshen] Ideally we use <Link> but this works for now
|
||||
if (!library) return
|
||||
@@ -147,7 +140,7 @@ const TopNavBar: FC = () => {
|
||||
</Button>
|
||||
<ul className="flex items-center">
|
||||
<li className="px-4">
|
||||
<div className="cursor-pointer" onClick={toggleDarkMode}>
|
||||
<div className="cursor-pointer" onClick={() => toggleTheme()}>
|
||||
{isDarkMode ? (
|
||||
<IconMoon
|
||||
size={16}
|
||||
|
||||
@@ -6,9 +6,10 @@ import { FC, useEffect, useState } from 'react'
|
||||
import { Button, IconCommand, IconGitHub, IconMoon, IconSearch, IconSun } from 'ui'
|
||||
import { REFERENCES } from '~/components/Navigation/Navigation.constants'
|
||||
|
||||
import SearchButton from '~/components/Search/SearchButton'
|
||||
import { getPageType } from '~/lib/helpers'
|
||||
|
||||
import SearchButton from '~/components/Search/SearchButton'
|
||||
|
||||
const TopNavBarRef: FC = () => {
|
||||
const { isDarkMode, toggleTheme } = useTheme()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
@@ -36,15 +37,6 @@ const TopNavBarRef: FC = () => {
|
||||
{ text: 'Reference', key: 'reference', link: '/reference' },
|
||||
]
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString())
|
||||
toggleTheme()
|
||||
|
||||
const key = localStorage.getItem('supabaseDarkMode')
|
||||
document.documentElement.className = key === 'true' ? 'dark' : ''
|
||||
document.documentElement.style.colorScheme = key === 'true' ? 'dark' : ''
|
||||
}
|
||||
|
||||
const onSelectVersion = (version: string) => {
|
||||
// [Joshen] Ideally we use <Link> but this works for now
|
||||
if (!library) return
|
||||
@@ -149,7 +141,7 @@ const TopNavBarRef: FC = () => {
|
||||
</ul>
|
||||
<ul className="flex items-center">
|
||||
<li className="px-4">
|
||||
<div className="cursor-pointer" onClick={toggleDarkMode}>
|
||||
<div className="cursor-pointer" onClick={() => toggleTheme()}>
|
||||
{isDarkMode ? (
|
||||
<IconMoon
|
||||
size={16}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { SessionContextProvider } from '@supabase/auth-helpers-react'
|
||||
import { ThemeProvider } from 'common/Providers'
|
||||
import { DefaultSeo } from 'next-seo'
|
||||
import Head from 'next/head'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { AppPropsWithLayout } from 'types'
|
||||
// import { CommandMenuProvider } from 'ui'
|
||||
import Favicons from '~/components/Favicons'
|
||||
import SearchProvider from '~/components/Search/SearchProvider'
|
||||
import SiteLayout from '~/layouts/SiteLayout'
|
||||
@@ -69,18 +68,22 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
<SessionContextProvider supabaseClient={supabase}>
|
||||
<ThemeProvider>
|
||||
<SearchProvider>
|
||||
{/* <CommandMenuProvider site="docs"> */}
|
||||
<SiteLayout>
|
||||
<Component {...pageProps} />
|
||||
</SiteLayout>
|
||||
{/* </CommandMenuProvider> */}
|
||||
</SearchProvider>
|
||||
</ThemeProvider>
|
||||
</SessionContextProvider>
|
||||
) : (
|
||||
<ThemeProvider>
|
||||
<SearchProvider>
|
||||
{/* <CommandMenuProvider site="docs"> */}
|
||||
<SiteLayout>
|
||||
<Component {...pageProps} />
|
||||
</SiteLayout>
|
||||
{/* </CommandMenuProvider> */}
|
||||
</SearchProvider>
|
||||
</ThemeProvider>
|
||||
)}
|
||||
|
||||
@@ -16,18 +16,22 @@ const BuiltExamples = () => {
|
||||
<p className="p">There are many example apps and starter projects to get going</p>
|
||||
<div className="flex justify-center gap-2 py-4">
|
||||
<Link href="/docs/guides/examples" as="/docs/guides/examples" passHref>
|
||||
<Button as="a" type="default" size="small">
|
||||
View all examples
|
||||
</Button>
|
||||
<a>
|
||||
<Button type="default" size="small">
|
||||
View all examples
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
<Link
|
||||
href="https://github.com/supabase/supabase/tree/master/examples"
|
||||
as="https://github.com/supabase/supabase/tree/master/examples"
|
||||
passHref
|
||||
>
|
||||
<Button as="a" type="default" icon={<IconGitHub />} size="small">
|
||||
Official github library
|
||||
</Button>
|
||||
<a>
|
||||
<Button type="default" icon={<IconGitHub />} size="small">
|
||||
Official github library
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -126,9 +126,7 @@ function SplitCodeBlockCarousel(props: SplitCodeBlockCarousel) {
|
||||
{extension.url && (
|
||||
<Link href={extension.url} as={extension.url}>
|
||||
<a className="ml-px">
|
||||
<Button as="a" type="default">
|
||||
View documentation
|
||||
</Button>
|
||||
<Button type="default">View documentation</Button>
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -4,14 +4,6 @@ import { IconSun, IconMoon } from 'ui'
|
||||
function DarkModeToggle() {
|
||||
const { isDarkMode, toggleTheme } = useTheme()
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString())
|
||||
toggleTheme()
|
||||
|
||||
const key = localStorage.getItem('supabaseDarkMode')
|
||||
document.documentElement.className = key === 'true' ? 'dark' : ''
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<IconSun className="text-scale-900" strokeWidth={2} />
|
||||
@@ -24,7 +16,7 @@ function DarkModeToggle() {
|
||||
isDarkMode ? 'bg-scale-500' : 'bg-scale-900'
|
||||
} mx-5
|
||||
`}
|
||||
onClick={() => toggleDarkMode()}
|
||||
onClick={() => toggleTheme()}
|
||||
>
|
||||
<span className="sr-only">Toggle Themes</span>
|
||||
<span
|
||||
|
||||
@@ -40,14 +40,18 @@ const Hero = () => {
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Link href="https://app.supabase.com" as="https://app.supabase.com" passHref>
|
||||
<Button as="a" size="small" className="text-white">
|
||||
Start your project
|
||||
</Button>
|
||||
<a>
|
||||
<Button size="small" className="text-white">
|
||||
Start your project
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/docs" as="/docs" passHref>
|
||||
<Button as="a" size="small" type="default" icon={<IconBookOpen />}>
|
||||
Documentation
|
||||
</Button>
|
||||
<a>
|
||||
<Button size="small" type="default" icon={<IconBookOpen />}>
|
||||
Documentation
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
|
||||
@@ -32,18 +32,22 @@ function GithubExamples() {
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-2 py-4">
|
||||
<Link href="/docs/guides/auth/overview" passHref>
|
||||
<Button size="small" as="a" type="default" icon={<IconBookOpen size={12} />}>
|
||||
View guides
|
||||
</Button>
|
||||
<a>
|
||||
<Button size="small" type="default" icon={<IconBookOpen size={12} />}>
|
||||
View guides
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
<Link
|
||||
href="https://github.com/supabase/supabase/tree/master/examples"
|
||||
as="https://github.com/supabase/supabase/tree/master/examples"
|
||||
passHref
|
||||
>
|
||||
<Button size="small" as="a" type="default" icon={<IconGitHub size={12} />}>
|
||||
Official GitHub library
|
||||
</Button>
|
||||
<a>
|
||||
<Button size="small" type="default" icon={<IconGitHub size={12} />}>
|
||||
Official GitHub library
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -37,28 +37,18 @@ function TwitterSocialProof() {
|
||||
</p>
|
||||
<div className="my-8 flex justify-center gap-2">
|
||||
<Link href={'https://github.com/supabase/supabase/discussions'} passHref>
|
||||
<Button
|
||||
as="a"
|
||||
// @ts-ignore
|
||||
target="_blank"
|
||||
size="small"
|
||||
iconRight={<IconMessageCircle size={14} />}
|
||||
type="default"
|
||||
>
|
||||
GitHub discussions
|
||||
</Button>
|
||||
<a target="_blank">
|
||||
<Button size="small" iconRight={<IconMessageCircle size={14} />} type="default">
|
||||
GitHub discussions
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={'https://discord.supabase.com/'} passHref>
|
||||
<Button
|
||||
as="a"
|
||||
type="default"
|
||||
// @ts-ignore
|
||||
target="_blank"
|
||||
size="small"
|
||||
iconRight={<IconMessageCircle size={14} />}
|
||||
>
|
||||
Discord
|
||||
</Button>
|
||||
<a target="_blank">
|
||||
<Button type="default" size="small" iconRight={<IconMessageCircle size={14} />}>
|
||||
Discord
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,9 +61,11 @@ const Error404 = () => {
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<Link href="/">
|
||||
<Button as="a" size="small" className="text-white">
|
||||
Head back
|
||||
</Button>
|
||||
<a>
|
||||
<Button size="small" className="text-white">
|
||||
Head back
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -298,9 +298,11 @@ function Database() {
|
||||
text="We only show a few of the extensions supported by supabase here, but we preinstall many more that you can use right away."
|
||||
/>
|
||||
<Link href="/docs/guides/database" passHref>
|
||||
<Button as="a" size="small" type="default" icon={<IconArrowUpRight />}>
|
||||
Explore documentation
|
||||
</Button>
|
||||
<a>
|
||||
<Button size="small" type="default" icon={<IconArrowUpRight />}>
|
||||
Explore documentation
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="col-span-12 mt-8 lg:col-span-6 lg:col-start-7 lg:mt-0">
|
||||
|
||||
@@ -178,9 +178,11 @@ export default function IndexPage() {
|
||||
</div>
|
||||
<p className="p">{description}</p>
|
||||
<Link href="https://app.supabase.com" passHref>
|
||||
<Button as="a" size="medium" block>
|
||||
Get started
|
||||
</Button>
|
||||
<a>
|
||||
<Button size="medium" block>
|
||||
Get started
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -145,9 +145,9 @@ function RealtimePage() {
|
||||
</p>
|
||||
<div className="not-prose mt-3">
|
||||
<Link passHref href="/docs/guides/realtime/postgres-changes">
|
||||
<Button as="a" type="default">
|
||||
View docs
|
||||
</Button>
|
||||
<a>
|
||||
<Button type="default">View docs</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -163,9 +163,9 @@ function RealtimePage() {
|
||||
<p>Store and synchronize online user state consistently across clients.</p>
|
||||
<div className="not-prose mt-3">
|
||||
<Link passHref href="/docs/guides/realtime/presence">
|
||||
<Button as="a" type="default">
|
||||
View docs
|
||||
</Button>
|
||||
<a>
|
||||
<Button type="default">View docs</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -181,9 +181,9 @@ function RealtimePage() {
|
||||
<p>Send any data to any client subscribed to the same Channel.</p>
|
||||
<div className="not-prose mt-3">
|
||||
<Link passHref href="/docs/guides/realtime/broadcast">
|
||||
<Button as="a" type="default">
|
||||
View docs
|
||||
</Button>
|
||||
<a>
|
||||
<Button type="default">View docs</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -130,9 +130,11 @@ As a database company, being SOC2 compliant is important when handling sensitive
|
||||
|
||||
<div className="not-prose">
|
||||
<Link passHref href="https://forms.supabase.com/soc2">
|
||||
<Button size="medium" type="default" as="a" target="_blank">
|
||||
Request SOC2 documents
|
||||
</Button>
|
||||
<a target="_blank">
|
||||
<Button size="medium" type="default">
|
||||
Request SOC2 documents
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -188,15 +188,16 @@ function StoragePage() {
|
||||
text="Serve from the edge to reduce latency."
|
||||
/>
|
||||
<Link href="/docs/guides/storage/cdn" passHref>
|
||||
<Button
|
||||
as="a"
|
||||
size="small"
|
||||
type="default"
|
||||
className="mt-4"
|
||||
icon={<IconArrowUpRight />}
|
||||
>
|
||||
Explore docs
|
||||
</Button>
|
||||
<a>
|
||||
<Button
|
||||
size="small"
|
||||
type="default"
|
||||
className="mt-4"
|
||||
icon={<IconArrowUpRight />}
|
||||
>
|
||||
Explore docs
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="col-span-6 lg:col-span-12 xl:col-span-4">
|
||||
@@ -206,15 +207,16 @@ function StoragePage() {
|
||||
text="Resize and compress your media before you serve it."
|
||||
/>
|
||||
<Link href="/docs/guides/storage/image-transformations" passHref>
|
||||
<Button
|
||||
as="a"
|
||||
size="small"
|
||||
type="default"
|
||||
className="mt-4"
|
||||
icon={<IconArrowUpRight />}
|
||||
>
|
||||
Explore docs
|
||||
</Button>
|
||||
<a>
|
||||
<Button
|
||||
size="small"
|
||||
type="default"
|
||||
className="mt-4"
|
||||
icon={<IconArrowUpRight />}
|
||||
>
|
||||
Explore docs
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>,
|
||||
@@ -252,15 +254,16 @@ function StoragePage() {
|
||||
</p>
|
||||
|
||||
<Link href="/docs/reference/javascript/storage-createbucket" passHref>
|
||||
<Button
|
||||
as="a"
|
||||
size="small"
|
||||
type="default"
|
||||
className="mt-4"
|
||||
icon={<IconArrowUpRight />}
|
||||
>
|
||||
Explore documentation
|
||||
</Button>
|
||||
<a>
|
||||
<Button
|
||||
size="small"
|
||||
type="default"
|
||||
className="mt-4"
|
||||
icon={<IconArrowUpRight />}
|
||||
>
|
||||
Explore documentation
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="col-span-12 lg:col-span-6 lg:col-start-7">
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/edge-functions</loc>
|
||||
<loc>https://supabase.com/legal/dpa</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
@@ -91,7 +91,7 @@
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/legal/dpa</loc>
|
||||
<loc>https://supabase.com/edge-functions</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
@@ -1058,6 +1058,30 @@
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/blog/case-study-happyteams</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/blog/type-constraints-in-65-lines-of-sql</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/blog/geo-queries-with-postgis-in-ionic-angular</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/blog/supabase-beta-update-february-2023</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/alternatives/supabase-vs-firebase</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
declare module 'sse.js' {
|
||||
export type SSEOptions = EventSourceInit & {
|
||||
headers?: Record<string, string>
|
||||
payload?: string
|
||||
method?: string
|
||||
}
|
||||
|
||||
export class SSE extends EventSource {
|
||||
constructor(url: string | URL, sseOptions?: SSEOptions)
|
||||
stream(): void
|
||||
}
|
||||
}
|
||||
Generated
+1666
-2431
File diff suppressed because it is too large.
Load diff
@@ -40,6 +40,7 @@
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"prettier": "^2.8.4",
|
||||
"prettier-plugin-sql-cst": "^0.5.0",
|
||||
"supabase": "^1.45.2",
|
||||
"ts-jest": "^27.0.7",
|
||||
"turbo": "^1.4.7"
|
||||
},
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import { createContext, useContext, useEffect, useState } from 'react'
|
||||
|
||||
interface UseThemeProps {
|
||||
export interface UseThemeProps {
|
||||
isDarkMode?: boolean
|
||||
toggleTheme: () => void
|
||||
/**
|
||||
* Toggle between dark mode and light mode.
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* If `darkMode` left `undefined`, toggles between modes.
|
||||
*
|
||||
* If `darkMode` set, forces
|
||||
* dark mode if `true` and light mode if `false`.
|
||||
*/
|
||||
toggleTheme: (darkMode?: boolean) => void
|
||||
}
|
||||
|
||||
interface ThemeProviderProps {
|
||||
@@ -25,8 +35,15 @@ export const ThemeProvider = ({ children }: ThemeProviderProps) => {
|
||||
setIsDarkMode(!key || key === 'true')
|
||||
}, [])
|
||||
|
||||
const toggleTheme = () => {
|
||||
setIsDarkMode(!isDarkMode)
|
||||
const toggleTheme: UseThemeProps['toggleTheme'] = (darkMode) => {
|
||||
const newMode = typeof darkMode === 'boolean' ? darkMode : !isDarkMode
|
||||
|
||||
localStorage.setItem('supabaseDarkMode', newMode.toString())
|
||||
|
||||
const key = localStorage.getItem('supabaseDarkMode')
|
||||
document.documentElement.className = key === 'true' ? 'dark' : ''
|
||||
|
||||
setIsDarkMode(newMode)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -225,6 +225,26 @@ const uiConfig = {
|
||||
'0%': { transform: 'translate-x-0', opacity: 1 },
|
||||
'100%': { transform: 'translateX(100%)', opacity: 0 },
|
||||
},
|
||||
lineLoading: {
|
||||
'0%': {
|
||||
marginLeft: '-10%',
|
||||
width: '80px',
|
||||
},
|
||||
'25%': {
|
||||
width: ' 240px',
|
||||
},
|
||||
'50%': {
|
||||
marginLeft: '100%',
|
||||
width: '80px',
|
||||
},
|
||||
'75%': {
|
||||
width: '240px',
|
||||
},
|
||||
'100%': {
|
||||
marginLeft: '-10%',
|
||||
width: '80px',
|
||||
},
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
'fade-in': 'fadeIn 300ms',
|
||||
@@ -250,6 +270,8 @@ const uiConfig = {
|
||||
'panel-slide-right-out': 'panelSlideRightOut 200ms cubic-bezier(0.87, 0, 0.13, 1)',
|
||||
'panel-slide-right-in': 'panelSlideRightIn 250ms cubic-bezier(0.87, 0, 0.13, 1)',
|
||||
|
||||
'line-loading': 'lineLoading 1.8s infinite',
|
||||
|
||||
// tailwind class for this is `animate-dropdownFadeIn`
|
||||
dropdownFadeIn: 'dropdownFadeIn 0.1s ease-out',
|
||||
// tailwind class for this is `animate-dropdownFadeOut`
|
||||
@@ -267,13 +289,17 @@ const uiConfig = {
|
||||
function ({ addUtilities, addVariant }) {
|
||||
// addVariant('data-open', '&:[data-state=open]')
|
||||
addUtilities({
|
||||
'.line-loading-bg': {
|
||||
background: 'rgb(0, 0, 0)',
|
||||
background:
|
||||
'linear-gradient(90deg,rgba(0, 0, 0, 0) 0%,rgba(255, 255, 255, 0.65) 50%,rgba(0, 0, 0, 0) 100%)',
|
||||
},
|
||||
".dropdown-content[data-state='open']": {
|
||||
animation: 'fadeIn 50ms ease-out',
|
||||
},
|
||||
".dropdown-content[data-state='closed']": {
|
||||
animation: 'fadeOut 50ms ease-in',
|
||||
},
|
||||
|
||||
"[data-state='open'] .accordion-content-animation": {
|
||||
animation: 'slideDown 200ms ease-out',
|
||||
},
|
||||
|
||||
@@ -55,6 +55,9 @@ export * from './src/components/Toggle'
|
||||
export * from './src/components/Upload'
|
||||
export * from './src/components/Form'
|
||||
|
||||
// CMD+K
|
||||
// export * from './src/components/Command'
|
||||
|
||||
// config
|
||||
|
||||
// export { default as Config } from './../ui.config'
|
||||
|
||||
@@ -10,24 +10,32 @@
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.0.0",
|
||||
"@mertasan/tailwindcss-variables": "^2.0.1",
|
||||
"@radix-ui/colors": "^0.1.8",
|
||||
"@radix-ui/react-accordion": "^0.1.5",
|
||||
"@radix-ui/react-collapsible": "^0.1.5",
|
||||
"@radix-ui/react-context-menu": "^0.1.0",
|
||||
"@radix-ui/react-dialog": "^0.1.5",
|
||||
"@radix-ui/react-dropdown-menu": "^0.1.4",
|
||||
"@radix-ui/react-popover": "^0.1.0",
|
||||
"@radix-ui/react-accordion": "^1.1.0",
|
||||
"@radix-ui/react-collapsible": "^1.0.1",
|
||||
"@radix-ui/react-context-menu": "^2.1.2",
|
||||
"@radix-ui/react-dialog": "^1.0.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.3",
|
||||
"@radix-ui/react-popover": "^1.0.4",
|
||||
"@radix-ui/react-portal": "^1.0.1",
|
||||
"@radix-ui/react-tabs": "^0.1.0",
|
||||
"@radix-ui/react-tabs": "^1.0.2",
|
||||
"@supabase/auth-helpers-react": "^0.3.1",
|
||||
"@tailwindcss/forms": "^0.4.0",
|
||||
"@tailwindcss/typography": "^0.5.0",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"clsx": "^1.2.1",
|
||||
"cmdk-supabase": "^0.2.2",
|
||||
"deepmerge": "^4.2.2",
|
||||
"formik": "^2.2.9",
|
||||
"lodash": "^4.17.20",
|
||||
"lucide-react": "^0.124.0",
|
||||
"openai": "^3.2.1",
|
||||
"postcss": "^8.4.5",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-feather": "^2.0.10",
|
||||
"react-markdown": "^8.0.3",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sse.js": "^0.6.1",
|
||||
"tailwind-merge": "^1.10.0",
|
||||
"tailwindcss": "^3.0.15",
|
||||
"tailwindcss-radix": "^1.6.0"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
import type {
|
||||
ChatCompletionResponseMessage,
|
||||
CreateChatCompletionResponse,
|
||||
CreateChatCompletionResponseChoicesInner,
|
||||
} from 'openai'
|
||||
import { useCallback, useEffect, useReducer, useRef, useState } from 'react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { SSE } from 'sse.js'
|
||||
|
||||
import { Button, IconAlertCircle, IconAlertTriangle, IconLoader, IconUser, Input } from 'ui'
|
||||
import { AiIcon, AiIconChat } from './Command.icons'
|
||||
import { CommandGroup, CommandItem } from './Command.utils'
|
||||
import { useCommandMenu } from './CommandMenuProvider'
|
||||
|
||||
const questions = [
|
||||
'How do I get started with Supabase?',
|
||||
'How do I run Supabase locally?',
|
||||
'How do I connect to my database?',
|
||||
'How do I run migrations? ',
|
||||
'How do I listen to changes in a table?',
|
||||
'How do I set up authentication?',
|
||||
]
|
||||
|
||||
type CreateChatCompletionResponseChoicesInnerDelta = Omit<
|
||||
CreateChatCompletionResponseChoicesInner,
|
||||
'message'
|
||||
> & {
|
||||
delta: Partial<ChatCompletionResponseMessage>
|
||||
}
|
||||
|
||||
function getEdgeFunctionUrl() {
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL?.replace(/\/$/, '')
|
||||
|
||||
if (!supabaseUrl) {
|
||||
throw new Error('Missing environment variable NEXT_PUBLIC_SUPABASE_URL')
|
||||
}
|
||||
|
||||
// https://github.com/supabase/supabase-js/blob/10d3423506cbd56345f7f6ab2ec2093c8db629d4/src/SupabaseClient.ts#L96
|
||||
const isPlatform = supabaseUrl.match(/(supabase\.co)|(supabase\.in)/)
|
||||
|
||||
if (isPlatform) {
|
||||
const [schemeAndProjectId, domain, tld] = supabaseUrl.split('.')
|
||||
return `${schemeAndProjectId}.functions.${domain}.${tld}`
|
||||
} else {
|
||||
return `${supabaseUrl}/functions/v1`
|
||||
}
|
||||
}
|
||||
|
||||
const edgeFunctionUrl = getEdgeFunctionUrl()
|
||||
|
||||
function promptDataReducer(
|
||||
state: any[],
|
||||
action: {
|
||||
index?: number
|
||||
answer?: string | undefined
|
||||
status?: string
|
||||
query?: string | undefined
|
||||
type?: 'remove-last-item' | string
|
||||
}
|
||||
) {
|
||||
// set a standard state to use later
|
||||
let current = [...state]
|
||||
|
||||
if (action.type) {
|
||||
switch (action.type) {
|
||||
case 'remove-last-item':
|
||||
current.pop()
|
||||
return [...current]
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// check that an index is present
|
||||
if (action.index === undefined) return [...state]
|
||||
|
||||
if (!current[action.index]) {
|
||||
current[action.index] = { query: '', answer: '', status: '' }
|
||||
}
|
||||
|
||||
current[action.index].answer = action.answer
|
||||
|
||||
if (action.query) {
|
||||
current[action.index].query = action.query
|
||||
}
|
||||
if (action.status) {
|
||||
current[action.index].status = action.status
|
||||
}
|
||||
|
||||
return [...current]
|
||||
}
|
||||
|
||||
const AiCommand = () => {
|
||||
const [answer, setAnswer] = useState<string | undefined>('')
|
||||
const [isResponding, setIsResponding] = useState(false)
|
||||
const [hasClippyError, setHasClippyError] = useState(false)
|
||||
const eventSourceRef = useRef<SSE>()
|
||||
const { isLoading, setIsLoading, currentPage, search, setSearch } = useCommandMenu()
|
||||
|
||||
const [promptIndex, setPromptIndex] = useState(0)
|
||||
const [promptData, dispatchPromptData] = useReducer(promptDataReducer, [])
|
||||
|
||||
const cantHelp = answer?.trim() === "Sorry, I don't know how to help with that."
|
||||
const status = isLoading
|
||||
? 'Clippy is searching...'
|
||||
: isResponding
|
||||
? 'Clippy is responding...'
|
||||
: cantHelp || hasClippyError
|
||||
? 'Clippy has failed you'
|
||||
: undefined
|
||||
|
||||
const handleConfirm = useCallback(
|
||||
async (query: string) => {
|
||||
setAnswer(undefined)
|
||||
setSearch('')
|
||||
dispatchPromptData({ index: promptIndex, answer: undefined, query })
|
||||
setIsResponding(false)
|
||||
setHasClippyError(false)
|
||||
setIsLoading(true)
|
||||
|
||||
const eventSource = new SSE(`${edgeFunctionUrl}/clippy-search`, {
|
||||
headers: {
|
||||
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? '',
|
||||
Authorization: `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
payload: JSON.stringify({ query }),
|
||||
})
|
||||
|
||||
function handleError<T>(err: T) {
|
||||
setIsLoading(false)
|
||||
setIsResponding(false)
|
||||
setHasClippyError(true)
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
eventSource.addEventListener('error', handleError)
|
||||
eventSource.addEventListener('message', (e: any) => {
|
||||
try {
|
||||
setIsLoading(false)
|
||||
|
||||
if (e.data === '[DONE]') {
|
||||
setIsResponding(false)
|
||||
setAnswer(undefined)
|
||||
setPromptIndex((x) => {
|
||||
return x + 1
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
setIsResponding(true)
|
||||
|
||||
const completionResponse: CreateChatCompletionResponse = JSON.parse(e.data)
|
||||
const [
|
||||
{
|
||||
delta: { content },
|
||||
},
|
||||
] = completionResponse.choices as CreateChatCompletionResponseChoicesInnerDelta[]
|
||||
|
||||
const text = content ?? ''
|
||||
|
||||
setAnswer((answer) => {
|
||||
const currentAnswer = answer ?? ''
|
||||
|
||||
dispatchPromptData({
|
||||
index: promptIndex,
|
||||
answer: currentAnswer + text,
|
||||
})
|
||||
|
||||
return (answer ?? '') + text
|
||||
})
|
||||
} catch (err) {
|
||||
handleError(err)
|
||||
}
|
||||
})
|
||||
|
||||
eventSource.stream()
|
||||
|
||||
eventSourceRef.current = eventSource
|
||||
|
||||
setIsLoading(true)
|
||||
},
|
||||
[promptIndex, promptData]
|
||||
)
|
||||
|
||||
function handleResetPrompt() {
|
||||
eventSourceRef.current?.close()
|
||||
eventSourceRef.current = undefined
|
||||
setSearch('')
|
||||
setAnswer(undefined)
|
||||
setIsResponding(false)
|
||||
setHasClippyError(false)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (search) {
|
||||
handleConfirm(search)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<div className="relative mb-[70px] py-4 overflow-y-auto overflow-hidden max-h-[720px]">
|
||||
<div className="flex flex-col gap-6">
|
||||
{promptData.map((prompt, i) => {
|
||||
if (!prompt.query) return <></>
|
||||
|
||||
return (
|
||||
<>
|
||||
{prompt.query && (
|
||||
<div className="flex gap-6 mx-4">
|
||||
<div className="w-7 h-7 bg-brand-900 rounded-full border border-brand-800 flex items-center justify-center text-brand-1200">
|
||||
<IconUser strokeWidth={2} size={16} />
|
||||
</div>
|
||||
<div className="prose text-scale-1000">{prompt.query}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="px-4">
|
||||
{cantHelp ? (
|
||||
<p className="flex flex-col gap-4 items-center p-4">
|
||||
<div className="grid md:flex items-center gap-2 mt-4 text-center justify-items-center">
|
||||
<IconAlertCircle />
|
||||
<p>Sorry, I don't know how to help with that.</p>
|
||||
</div>
|
||||
<Button size="tiny" type="secondary" onClick={handleResetPrompt}>
|
||||
Try again?
|
||||
</Button>
|
||||
</p>
|
||||
) : (
|
||||
<div className="flex gap-6">
|
||||
<AiIconChat />
|
||||
<div className="w-full">
|
||||
{isLoading && promptIndex === i ? (
|
||||
<div className="bg-scale-700 h-[21px] w-[13px] mt-1 animate-pulse animate-bounce"></div>
|
||||
) : (
|
||||
// TODO: pull in markdown components from docs for better styling (code blocks, etc)
|
||||
<ReactMarkdown
|
||||
linkTarget="_blank"
|
||||
className="prose dark:prose-dark"
|
||||
remarkPlugins={[remarkGfm]}
|
||||
transformLinkUri={(href) => {
|
||||
const supabaseUrl = new URL('https://supabase.com')
|
||||
const linkUrl = new URL(href, 'https://supabase.com')
|
||||
|
||||
if (linkUrl.origin === supabaseUrl.origin) {
|
||||
return linkUrl.toString()
|
||||
}
|
||||
|
||||
return href
|
||||
}}
|
||||
>
|
||||
{prompt.answer}
|
||||
</ReactMarkdown>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{promptData.length === 0 && !hasClippyError && (
|
||||
<CommandGroup heading="Examples" forceMount>
|
||||
{questions.map((question) => {
|
||||
const key = question.replace(/\s+/g, '_')
|
||||
return (
|
||||
<CommandItem
|
||||
type="command"
|
||||
onSelect={() => {
|
||||
if (!search) {
|
||||
handleConfirm(question)
|
||||
}
|
||||
}}
|
||||
forceMount
|
||||
key={key}
|
||||
>
|
||||
<AiIcon />
|
||||
{question}
|
||||
</CommandItem>
|
||||
)
|
||||
})}
|
||||
</CommandGroup>
|
||||
)}
|
||||
{hasClippyError && (
|
||||
<div className="p-6 flex flex-col items-center gap-6 mt-4">
|
||||
<IconAlertTriangle className="text-amber-900" strokeWidth={1.5} size={21} />
|
||||
<p className="text-lg text-scale-1200 text-center">
|
||||
Sorry, looks like Clippy is having a hard time!
|
||||
</p>
|
||||
<p className="text-sm text-scale-900 text-center">Please try again in a bit.</p>
|
||||
<Button size="tiny" type="secondary" onClick={handleResetPrompt}>
|
||||
Try again?
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute bottom-0 w-full bg-scale-200">
|
||||
<Input
|
||||
className="bg-scale-100 rounded mx-3"
|
||||
autoFocus
|
||||
type="textarea"
|
||||
placeholder="Ask a question"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
if (!search) {
|
||||
return
|
||||
}
|
||||
handleConfirm(search)
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="text-scale-1100 px-3">
|
||||
<div className="flex justify-between items-center text-xs">
|
||||
<div className="flex items-center gap-1 py-2 text-scale-800">
|
||||
<span>Powered by OpenAI.</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-6 py-1">
|
||||
{status && (
|
||||
<span className="bg-scale-400 rounded-lg py-1 px-2 items-center gap-2 hidden md:flex">
|
||||
{(isLoading || isResponding) && <IconLoader size={14} className="animate-spin" />}
|
||||
{status}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AiCommand
|
||||
@@ -0,0 +1,46 @@
|
||||
import React from 'react'
|
||||
export const AiIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className="w-6 h-6 text-purple-900"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const AiIconChat = () => (
|
||||
<div
|
||||
className="w-7 h-7
|
||||
bg-gradient-to-r from-purple-900 to-purple-800
|
||||
|
||||
ring-purple-600
|
||||
ring-1
|
||||
|
||||
rounded-md border border-purple-400 flex items-center justify-center
|
||||
shadow-sm
|
||||
"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className="w-4 h-4 text-white"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
export interface NavItem {
|
||||
label: string;
|
||||
icon: string;
|
||||
url: string;
|
||||
sites: Site[];
|
||||
subItems?: NavItem[];
|
||||
}
|
||||
|
||||
export type Site = "docs" | "dashboard";
|
||||
|
||||
export interface NavItems {
|
||||
navItems: NavItem[];
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
import { Command as CommandPrimitive } from 'cmdk-supabase'
|
||||
import * as React from 'react'
|
||||
|
||||
import { cn } from './../../utils/cn'
|
||||
|
||||
import { DetailedHTMLProps, HTMLAttributes, KeyboardEventHandler } from 'react'
|
||||
import { Modal } from '../Modal'
|
||||
import { ModalProps } from '../Modal/Modal'
|
||||
import { useCommandMenu } from './CommandMenuProvider'
|
||||
import { LoadingLine } from './LoadingLine'
|
||||
|
||||
type CommandPrimitiveElement = React.ElementRef<typeof CommandPrimitive>
|
||||
type CommandPrimitiveProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
||||
|
||||
export const Command = React.forwardRef<CommandPrimitiveElement, CommandPrimitiveProps>(
|
||||
({ className, ...props }, ref) => (
|
||||
<CommandPrimitive
|
||||
ref={ref}
|
||||
className={cn('flex h-full w-full flex-col overflow-hidden', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
|
||||
Command.displayName = CommandPrimitive.displayName
|
||||
|
||||
export interface CommandDialogProps extends ModalProps {
|
||||
onKeyDown?: KeyboardEventHandler<HTMLDivElement>
|
||||
page?: number | string
|
||||
}
|
||||
|
||||
export const CommandDialog = ({ children, onKeyDown, page, ...props }: CommandDialogProps) => {
|
||||
const [animateBounce, setAnimateBounce] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
setAnimateBounce(true)
|
||||
setTimeout(() => setAnimateBounce(false), 126)
|
||||
}, [page])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
{...props}
|
||||
hideFooter
|
||||
className={cn(
|
||||
'!bg-[#f8f9fa]/80 dark:!bg-[#1c1c1c]/80 backdrop-filter backdrop-blur-sm',
|
||||
'!border-[#e6e8eb]/90 dark:!border-[#282828]/90',
|
||||
'transition ease-out',
|
||||
animateBounce ? 'scale-[101.5%]' : 'scale-100'
|
||||
)}
|
||||
>
|
||||
<Command
|
||||
className={[
|
||||
'[&_[cmdk-group]]:px-2 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-scale-800 [&_[cmdk-input]]:h-12',
|
||||
|
||||
'[&_[cmdk-item]_svg]:mr-3',
|
||||
'[&_[cmdk-item]_svg]:h-5',
|
||||
'[&_[cmdk-item]_svg]:w-5',
|
||||
'[&_[cmdk-input-wrapper]_svg]:h-5',
|
||||
'[&_[cmdk-input-wrapper]_svg]:w-5',
|
||||
|
||||
'[&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0',
|
||||
].join(' ')}
|
||||
>
|
||||
{children}
|
||||
</Command>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
CommandDialog.displayName = 'CommandDialog'
|
||||
|
||||
type CommandPrimitiveInputElement = React.ElementRef<typeof CommandPrimitive.Input>
|
||||
type CommandPrimitiveInputProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||
|
||||
export const CommandInput = React.forwardRef<
|
||||
CommandPrimitiveInputElement,
|
||||
CommandPrimitiveInputProps
|
||||
>(({ className, value, onValueChange, ...props }, ref) => {
|
||||
const { isLoading } = useCommandMenu()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center" cmdk-input-wrapper="">
|
||||
<CommandPrimitive.Input
|
||||
value={value}
|
||||
autoFocus
|
||||
onValueChange={onValueChange}
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex h-11 w-full rounded-md bg-transparent px-4 py-7 text-sm outline-none',
|
||||
'focus:shadow-none focus:ring-transparent',
|
||||
'placeholder:text-scale-800 disabled:cursor-not-allowed disabled:opacity-50 dark:text-scale-1200 border-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<LoadingLine loading={isLoading} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
CommandInput.displayName = CommandPrimitive.Input.displayName
|
||||
|
||||
type CommandPrimitiveListElement = React.ElementRef<typeof CommandPrimitive.List>
|
||||
type CommandPrimitiveListProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
||||
|
||||
export const CommandList = React.forwardRef<CommandPrimitiveListElement, CommandPrimitiveListProps>(
|
||||
({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.List
|
||||
ref={ref}
|
||||
className={cn('overflow-y-auto overflow-x-hidden', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
|
||||
CommandList.displayName = CommandPrimitive.List.displayName
|
||||
|
||||
type CommandPrimitiveEmptyElement = React.ElementRef<typeof CommandPrimitive.Empty>
|
||||
type CommandPrimitiveEmptyProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
||||
|
||||
export const CommandEmpty = React.forwardRef<
|
||||
CommandPrimitiveEmptyElement,
|
||||
CommandPrimitiveEmptyProps
|
||||
>((props, ref) => (
|
||||
<CommandPrimitive.Empty
|
||||
ref={ref}
|
||||
className="py-6 text-center text-sm text-scale-900"
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
||||
|
||||
type CommandPrimitiveGroupElement = React.ElementRef<typeof CommandPrimitive.Group>
|
||||
type CommandPrimitiveGroupProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
||||
|
||||
export const CommandGroup = React.forwardRef<
|
||||
CommandPrimitiveGroupElement,
|
||||
CommandPrimitiveGroupProps
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Group
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'overflow-hidden py-3 px-2 text-scale-700 dark:text-scale-800 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:pb-1.5 [&_[cmdk-group-heading]]:text-sm [&_[cmdk-group-heading]]:font-normal [&_[cmdk-group-heading]]:text-scale-900 [&_[cmdk-group-heading]]:dark:text-sca-300',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
||||
|
||||
type CommandPrimitiveSeparatorElement = React.ElementRef<typeof CommandPrimitive.Separator>
|
||||
type CommandPrimitiveSeparatorProps = React.ComponentPropsWithoutRef<
|
||||
typeof CommandPrimitive.Separator
|
||||
>
|
||||
|
||||
export const CommandSeparator = React.forwardRef<
|
||||
CommandPrimitiveSeparatorElement,
|
||||
CommandPrimitiveSeparatorProps
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
`h-px
|
||||
w-full
|
||||
bg-scale-50
|
||||
`,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
||||
|
||||
type CommandPrimitiveItemElement = React.ElementRef<typeof CommandPrimitive.Item>
|
||||
type CommandPrimitiveItemProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
||||
|
||||
export interface CommandItemProps extends CommandPrimitiveItemProps {
|
||||
type: 'link' | 'block-link' | 'command'
|
||||
}
|
||||
|
||||
export const CommandItem = React.forwardRef<CommandPrimitiveItemElement, CommandItemProps>(
|
||||
({ className, type, ...props }, ref) => (
|
||||
<CommandPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'cursor-default',
|
||||
'select-none',
|
||||
'items-center',
|
||||
'rounded-md',
|
||||
'text-sm',
|
||||
'group',
|
||||
'py-3',
|
||||
'text-scale-1100',
|
||||
'relative',
|
||||
'flex',
|
||||
type === 'block-link'
|
||||
? `
|
||||
bg-[#fbfcfd]/90
|
||||
dark:bg-[#232323]/90
|
||||
border
|
||||
border-[#ddd]/90
|
||||
dark:border-[#282828]/90
|
||||
backdrop-filter
|
||||
backdrop-blur-md
|
||||
px-5
|
||||
transition-all
|
||||
outline-none
|
||||
aria-selected:border-[#ccc]
|
||||
dark:aria-selected:border-[#323232]
|
||||
aria-selected:bg-[#f1f3f5]/90
|
||||
dark:aria-selected:bg-[#323232]
|
||||
aria-selected:shadow-sm
|
||||
aria-selected:scale-[100.3%]
|
||||
data-[disabled]:pointer-events-none data-[disabled]:opacity-50`
|
||||
: type === 'link'
|
||||
? `
|
||||
px-2
|
||||
backdrop-filter
|
||||
backdrop-blur-md
|
||||
transition-all
|
||||
outline-none
|
||||
aria-selected:bg-[#f1f3f5]/90
|
||||
dark:aria-selected:bg-[#323232]
|
||||
data-[disabled]:pointer-events-none data-[disabled]:opacity-50`
|
||||
: `
|
||||
px-2
|
||||
aria-selected:bg-scale-300
|
||||
dark:aria-selected:bg-[#323232]/80
|
||||
aria-selected:backdrop-filter
|
||||
aria-selected:backdrop-blur-md
|
||||
data-[disabled]:pointer-events-none
|
||||
data-[disabled]:opacity-50
|
||||
`,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
|
||||
CommandItem.displayName = CommandPrimitive.Item.displayName
|
||||
|
||||
export const CommandItemStale = React.forwardRef<CommandPrimitiveItemElement, CommandItemProps>(
|
||||
({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'text-scale-1100 relative flex cursor-default select-none items-center rounded-md py-1.5 px-2 text-sm outline-none aria-selected:bg-scale-500 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:aria-selected:bg-scale-500',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
|
||||
CommandItemStale.displayName = 'CommandItemStale'
|
||||
|
||||
export const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'[&:not(:last-child)]:hover:bg-scale-600 [&:not(:last-child)]:hover:cursor-pointer',
|
||||
'cursor-default bg-scale-500 px-1.5 py-0.5 rounded text-xs text-scale-900',
|
||||
'last:bg-scale-600 last:text-scale-900',
|
||||
'justify-end',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
CommandShortcut.displayName = 'CommandShortcut'
|
||||
|
||||
export const CommandLabel = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return <span {...props} className={cn('grow', className)} />
|
||||
}
|
||||
|
||||
CommandShortcut.displayName = 'CommandLabel'
|
||||
|
||||
export interface TextHighlighterProps
|
||||
extends DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> {
|
||||
text: string | undefined
|
||||
query: string | undefined
|
||||
}
|
||||
|
||||
export const TextHighlighter = ({ text, query, ...props }: TextHighlighterProps) => {
|
||||
const highlightMatches = (text?: string) => {
|
||||
if (!text) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!query) {
|
||||
return text
|
||||
}
|
||||
|
||||
const regex = new RegExp(query, 'gi')
|
||||
return text.replace(regex, (match) => `<span class="font-bold text-scale-1200">${match}</span>`)
|
||||
}
|
||||
|
||||
return <span dangerouslySetInnerHTML={{ __html: highlightMatches(text) }} {...props} />
|
||||
}
|
||||
|
||||
TextHighlighter.displayName = 'TextHighlighter'
|
||||
@@ -0,0 +1,237 @@
|
||||
import { useCommandState } from 'cmdk-supabase'
|
||||
import { useRouter } from 'next/router'
|
||||
import * as React from 'react'
|
||||
import { ElementRef, useRef } from 'react'
|
||||
import { IconHome } from '../Icon/icons/IconHome'
|
||||
|
||||
import { IconArrowRight } from './../Icon/icons/IconArrowRight'
|
||||
import { IconBook } from './../Icon/icons/IconBook'
|
||||
import { IconColumns } from './../Icon/icons/IconColumns'
|
||||
import { IconInbox } from './../Icon/icons/IconInbox'
|
||||
import { IconLifeBuoy } from './../Icon/icons/IconLifeBuoy'
|
||||
import { IconMonitor } from './../Icon/icons/IconMonitor'
|
||||
import { IconPhone } from './../Icon/icons/IconPhone'
|
||||
import { IconUser } from './../Icon/icons/IconUser'
|
||||
|
||||
import AiCommand from './AiCommand'
|
||||
import navItems from './utils/docs-nav-items.json'
|
||||
import sharedItems from './utils/shared-nav-items.json'
|
||||
import { AiIcon } from './Command.icons'
|
||||
import {
|
||||
CommandDialog,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandLabel,
|
||||
CommandList,
|
||||
CommandShortcut,
|
||||
} from './Command.utils'
|
||||
import { useCommandMenu } from './CommandMenuProvider'
|
||||
import DocsSearch from './DocsSearch'
|
||||
import DashboardTableEditor from './sections/DashboardTableEditor'
|
||||
import CommandMenuShortcuts from './CommandMenuShortcuts'
|
||||
import SearchOnlyItem from './SearchOnlyItem'
|
||||
import SearchableStudioItems from './SearchableStudioItems'
|
||||
|
||||
export const COMMAND_ROUTES = {
|
||||
AI: 'Supabase AI',
|
||||
DOCS_SEARCH: 'Docs Search',
|
||||
THEME: 'Theme',
|
||||
AI_ASK_ANYTHING: 'Ask anything',
|
||||
AI_RLS_POLICY: 'Help me create a RLS policy',
|
||||
}
|
||||
|
||||
export const CHAT_ROUTES = [
|
||||
COMMAND_ROUTES.AI, // this one is temporary
|
||||
COMMAND_ROUTES.AI_ASK_ANYTHING,
|
||||
COMMAND_ROUTES.AI_RLS_POLICY,
|
||||
]
|
||||
|
||||
const iconPicker: { [key: string]: React.ReactNode } = {
|
||||
arrowRight: <IconArrowRight />,
|
||||
book: <IconBook />,
|
||||
inbox: <IconInbox />,
|
||||
mobile: <IconPhone />,
|
||||
person: <IconUser />,
|
||||
services: <IconColumns />,
|
||||
contact: <IconMonitor />,
|
||||
icon: <IconHome />,
|
||||
products: <IconColumns />,
|
||||
}
|
||||
|
||||
const projectRef = ''
|
||||
|
||||
const CommandMenu = () => {
|
||||
const router = useRouter()
|
||||
|
||||
const commandInputRef = useRef<ElementRef<typeof CommandInput>>(null)
|
||||
const { isOpen, setIsOpen, actions, search, setSearch, pages, setPages, currentPage, site } =
|
||||
useCommandMenu()
|
||||
|
||||
console.log('currentPage page', currentPage)
|
||||
console.log('pages page', pages)
|
||||
|
||||
const ThemeOptions = ({ isSubItem = false }) => {
|
||||
return (
|
||||
<CommandGroup>
|
||||
<SearchOnlyItem
|
||||
isSubItem={isSubItem}
|
||||
onSelect={() => {
|
||||
actions.toggleTheme(true)
|
||||
setIsOpen(false)
|
||||
}}
|
||||
>
|
||||
Change Theme to dark
|
||||
</SearchOnlyItem>
|
||||
<SearchOnlyItem
|
||||
isSubItem={isSubItem}
|
||||
onSelect={() => {
|
||||
actions.toggleTheme(false)
|
||||
setIsOpen(false)
|
||||
}}
|
||||
>
|
||||
Change Theme to light
|
||||
</SearchOnlyItem>
|
||||
</CommandGroup>
|
||||
)
|
||||
}
|
||||
|
||||
const showCommandInput = !currentPage || !CHAT_ROUTES.includes(currentPage)
|
||||
|
||||
return (
|
||||
<>
|
||||
<CommandDialog
|
||||
page={currentPage}
|
||||
visible={isOpen}
|
||||
onInteractOutside={() => {
|
||||
setIsOpen(!open)
|
||||
}}
|
||||
size={'xlarge'}
|
||||
className={'max-h-[70vh] lg:max-h-[50vh] overflow-hidden overflow-y-auto'}
|
||||
>
|
||||
{pages.length > 0 && <CommandMenuShortcuts />}
|
||||
{showCommandInput && (
|
||||
<CommandInput
|
||||
ref={commandInputRef}
|
||||
placeholder="Type a command or search..."
|
||||
value={search}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
)}
|
||||
<CommandList className={['my-2', showCommandInput && 'max-h-[300px]'].join(' ')}>
|
||||
{!currentPage && (
|
||||
<>
|
||||
<CommandGroup heading="Documentation" forceMount>
|
||||
<CommandItem
|
||||
type="command"
|
||||
onSelect={() => {
|
||||
setPages([...pages, COMMAND_ROUTES.AI])
|
||||
}}
|
||||
forceMount
|
||||
>
|
||||
<AiIcon />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-purple-900 to-purple-1100">
|
||||
Ask Supabase AI
|
||||
{search ? (
|
||||
<>
|
||||
{': '}
|
||||
<span className="text-scale-1200 font-semibold">{search}</span>
|
||||
</>
|
||||
) : (
|
||||
'...'
|
||||
)}
|
||||
</span>
|
||||
</CommandItem>
|
||||
<CommandItem
|
||||
type="command"
|
||||
onSelect={() => setPages([...pages, COMMAND_ROUTES.DOCS_SEARCH])}
|
||||
forceMount
|
||||
>
|
||||
<IconBook className="" />
|
||||
|
||||
<span>
|
||||
Search the docs
|
||||
{search ? (
|
||||
<>
|
||||
{': '}
|
||||
<span className="text-scale-1200 font-semibold">{search}</span>
|
||||
</>
|
||||
) : (
|
||||
'...'
|
||||
)}
|
||||
</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
|
||||
{site === 'docs' && (
|
||||
<CommandGroup heading="Studio tools">
|
||||
{sharedItems.tools.map((item) => (
|
||||
<CommandItem key={item.url} type="link" onSelect={() => router.push(item.url)}>
|
||||
<IconArrowRight className="text-scale-900" />
|
||||
<CommandLabel>
|
||||
Go to <span className="font-bold"> {item.label}</span>
|
||||
</CommandLabel>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
)}
|
||||
|
||||
{site === 'studio' && (
|
||||
<CommandGroup heading="Navigate">
|
||||
{sharedItems.tools.map((item) => {
|
||||
const itemUrl = projectRef ? item.url.replace('_', projectRef) : item.url
|
||||
|
||||
return (
|
||||
<CommandItem key={item.url} type="link" onSelect={() => router.push(itemUrl)}>
|
||||
<IconArrowRight className="text-scale-900" />
|
||||
<CommandLabel>
|
||||
Go to <span className="font-bold"> {item.label}</span>
|
||||
</CommandLabel>
|
||||
</CommandItem>
|
||||
)
|
||||
})}
|
||||
</CommandGroup>
|
||||
)}
|
||||
|
||||
{/* <DashboardTableEditor /> */}
|
||||
|
||||
<CommandGroup heading="Support">
|
||||
{navItems.docsSupport.map((item) => (
|
||||
<CommandItem key={item.url} type="link" onSelect={() => router.push(item.url)}>
|
||||
<IconLifeBuoy className="text-scale-900" />
|
||||
<CommandLabel>
|
||||
Go to <span className="font-bold"> {item.label}</span>
|
||||
</CommandLabel>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
|
||||
<CommandGroup heading="General">
|
||||
{navItems.docsGeneral.map((item) => (
|
||||
<CommandItem key={item.url} type="link" onSelect={() => router.push(item.url)}>
|
||||
{item?.icon && iconPicker[item.icon]}
|
||||
<CommandLabel>{item.label}</CommandLabel>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
|
||||
<CommandGroup heading="Settings">
|
||||
<CommandItem type="link" onSelect={() => setPages([...pages, 'Theme'])}>
|
||||
<IconMonitor className="mr-2" />
|
||||
Change theme
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
<ThemeOptions isSubItem />
|
||||
{site === 'studio' && <SearchableStudioItems />}
|
||||
</>
|
||||
)}
|
||||
{currentPage === COMMAND_ROUTES.AI && <AiCommand />}
|
||||
{currentPage === COMMAND_ROUTES.DOCS_SEARCH && <DocsSearch />}
|
||||
{currentPage === COMMAND_ROUTES.THEME && <ThemeOptions />}
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CommandMenu
|
||||
@@ -0,0 +1,111 @@
|
||||
import { useTheme, UseThemeProps } from 'common'
|
||||
import * as React from 'react'
|
||||
import { createContext, PropsWithChildren, useContext, useEffect, useState } from 'react'
|
||||
import CommandMenu from './CommandMenu'
|
||||
|
||||
export interface CommandMenuContextValue {
|
||||
isOpen: boolean
|
||||
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>
|
||||
isLoading: boolean
|
||||
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>
|
||||
actions: CommandMenuActions
|
||||
search: string
|
||||
setSearch: React.Dispatch<React.SetStateAction<string>>
|
||||
pages: string[]
|
||||
setPages: React.Dispatch<React.SetStateAction<string[]>>
|
||||
currentPage?: string
|
||||
site: 'studio' | 'docs'
|
||||
}
|
||||
export const CommandMenuContext = createContext<CommandMenuContextValue | undefined>(undefined)
|
||||
export const useCommandMenu = () => {
|
||||
const context = useContext(CommandMenuContext)
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error('useCommandMenu was used outside of CommandMenuProvider')
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
export interface CommandMenuActions {
|
||||
toggleTheme: UseThemeProps['toggleTheme']
|
||||
}
|
||||
|
||||
const CommandMenuProvider = ({
|
||||
children,
|
||||
site,
|
||||
}: PropsWithChildren<{ site: 'studio' | 'docs' }>) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [search, setSearch] = React.useState('')
|
||||
const [pages, setPages] = React.useState<string[]>([])
|
||||
const { toggleTheme } = useTheme()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
|
||||
const actions: CommandMenuActions = {
|
||||
toggleTheme,
|
||||
}
|
||||
|
||||
useKeyboardEvents({ setIsOpen, currentPage, setSearch, setPages })
|
||||
|
||||
return (
|
||||
<CommandMenuContext.Provider
|
||||
value={{
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
actions,
|
||||
setSearch,
|
||||
search,
|
||||
pages,
|
||||
setPages,
|
||||
currentPage,
|
||||
site,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<CommandMenu />
|
||||
</CommandMenuContext.Provider>
|
||||
)
|
||||
}
|
||||
function useKeyboardEvents({
|
||||
setIsOpen,
|
||||
currentPage,
|
||||
setSearch,
|
||||
setPages,
|
||||
}: {
|
||||
setIsOpen: (isOpen: boolean) => void
|
||||
setSearch: React.Dispatch<React.SetStateAction<string>>
|
||||
setPages: React.Dispatch<React.SetStateAction<string[]>>
|
||||
currentPage?: string
|
||||
}) {
|
||||
useEffect(() => {
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
case 'Escape':
|
||||
event.preventDefault()
|
||||
// if on homepage, close the command palette
|
||||
if (!currentPage) setIsOpen(false)
|
||||
setSearch('')
|
||||
// if NOT on homepage, return to last page
|
||||
setPages((pages) => pages.slice(0, -1))
|
||||
return
|
||||
case 'k':
|
||||
case '/':
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
setIsOpen(true)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown)
|
||||
}
|
||||
}, [setIsOpen, currentPage])
|
||||
}
|
||||
|
||||
export default CommandMenuProvider
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react'
|
||||
import { CommandShortcut } from './Command.utils'
|
||||
import { useCommandMenu } from './CommandMenuProvider'
|
||||
|
||||
export default function CommandMenuShortcuts() {
|
||||
const { setPages, pages } = useCommandMenu()
|
||||
return (
|
||||
<div className="flex w-full gap-2 px-4 pt-4 justify-items-start flex-row">
|
||||
<CommandShortcut onClick={() => setPages([])}>{'Home'}</CommandShortcut>
|
||||
{pages.map((page, index) => (
|
||||
<CommandShortcut
|
||||
key={page}
|
||||
onClick={() => {
|
||||
if (index === pages.length - 1) {
|
||||
return
|
||||
}
|
||||
setPages(pages.slice(0, index - 1))
|
||||
}}
|
||||
>
|
||||
{page}
|
||||
</CommandShortcut>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
import {
|
||||
Button,
|
||||
IconAlertTriangle,
|
||||
IconBook,
|
||||
IconChevronRight,
|
||||
IconHash,
|
||||
IconSearch,
|
||||
useCommandMenu,
|
||||
} from 'ui'
|
||||
import { CommandGroup, CommandItem, CommandLabel, TextHighlighter } from './Command.utils'
|
||||
|
||||
import { debounce } from 'lodash'
|
||||
|
||||
const questions = [
|
||||
'How do I get started with Supabase?',
|
||||
'How do I run Supabase locally?',
|
||||
'How do I connect to my database?',
|
||||
'How do I run migrations? ',
|
||||
'How do I listen to changes in a table?',
|
||||
'How do I set up authentication?',
|
||||
]
|
||||
|
||||
export enum PageType {
|
||||
Markdown = 'markdown',
|
||||
Reference = 'reference',
|
||||
}
|
||||
|
||||
export interface PageSection {
|
||||
slug: string
|
||||
heading: string
|
||||
}
|
||||
|
||||
export interface PageMetadata {
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface PageResult {
|
||||
type: PageType
|
||||
path: string
|
||||
meta: PageMetadata
|
||||
sections: PageSection[]
|
||||
}
|
||||
|
||||
const DocsSearch = () => {
|
||||
const [results, setResults] = useState<PageResult[]>()
|
||||
const [hasSearchError, setHasSearchError] = useState(false)
|
||||
const supabaseClient = useSupabaseClient()
|
||||
const { isLoading, setIsLoading, search, setSearch } = useCommandMenu()
|
||||
|
||||
const handleSearch = useCallback(
|
||||
async (query: string) => {
|
||||
setHasSearchError(false)
|
||||
setIsLoading(true)
|
||||
|
||||
const { error, data: pageSections } = await supabaseClient.functions.invoke('search', {
|
||||
body: { query },
|
||||
})
|
||||
|
||||
setIsLoading(false)
|
||||
|
||||
if (error) {
|
||||
setIsLoading(false)
|
||||
|
||||
setHasSearchError(true)
|
||||
console.error(error)
|
||||
return
|
||||
}
|
||||
|
||||
if (!Array.isArray(pageSections)) {
|
||||
setIsLoading(false)
|
||||
setHasSearchError(true)
|
||||
console.error('Malformed response')
|
||||
return
|
||||
}
|
||||
|
||||
setResults(pageSections)
|
||||
},
|
||||
[supabaseClient]
|
||||
)
|
||||
|
||||
function handleResetPrompt() {
|
||||
setSearch('')
|
||||
setResults(undefined)
|
||||
setHasSearchError(false)
|
||||
}
|
||||
|
||||
const debouncedSearch = useMemo(() => debounce(handleSearch, 1000), [handleSearch])
|
||||
|
||||
// Search initial query immediately (note - empty useEffect deps)
|
||||
useEffect(() => {
|
||||
if (search) {
|
||||
handleSearch(search)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// TODO: can we do this w/o useEffect if query comes from context?
|
||||
useEffect(() => {
|
||||
if (search) {
|
||||
debouncedSearch(search)
|
||||
}
|
||||
}, [search])
|
||||
|
||||
const ChevronArrow = () => (
|
||||
<IconChevronRight
|
||||
strokeWidth={1.5}
|
||||
className="
|
||||
text-scale-900
|
||||
opacity-0
|
||||
-left-4
|
||||
group-aria-selected:scale-[101%]
|
||||
group-aria-selected:opacity-100
|
||||
group-aria-selected:left-0
|
||||
"
|
||||
/>
|
||||
)
|
||||
|
||||
const IconContainer = (
|
||||
props: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
|
||||
) => (
|
||||
<div
|
||||
className="
|
||||
transition
|
||||
w-6 h-6
|
||||
bg-scale-100
|
||||
group-aria-selected:scale-[105%]
|
||||
group-aria-selected:bg-scale-1200
|
||||
text-scale-1200
|
||||
group-aria-selected:text-scale-100
|
||||
rounded flex
|
||||
items-center
|
||||
justify-center
|
||||
|
||||
group-aria-selected:[&_svg]:scale-[103%]
|
||||
"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{results &&
|
||||
results.length > 0 &&
|
||||
results.map((page, i) => {
|
||||
const pageSections = page.sections.filter((section) => !!section.heading)
|
||||
return (
|
||||
<CommandGroup
|
||||
heading=""
|
||||
forceMount
|
||||
key={`${page.meta.title}-group-index-${i}`}
|
||||
value={`${page.meta.title}-group-index-${i}`}
|
||||
>
|
||||
<CommandItem
|
||||
forceMount
|
||||
key={`${page.meta.title}-item-index-${i}`}
|
||||
value={`${page.meta.title}-item-index-${i}`}
|
||||
type="block-link"
|
||||
onSelect={() => {
|
||||
// TODO: replace with Next.js router/Link when cross-project link logic solved
|
||||
window.location.assign(`/docs/${page.path}`)
|
||||
}}
|
||||
>
|
||||
<div className="grow flex gap-3 items-center">
|
||||
<IconContainer>
|
||||
<IconBook strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
|
||||
</IconContainer>
|
||||
<div className="flex flex-col gap-0">
|
||||
<CommandLabel>
|
||||
<TextHighlighter text={page.meta.title} query={search} />
|
||||
</CommandLabel>
|
||||
<div className="text-xs text-scale-900">
|
||||
<TextHighlighter text={page.meta.description} query={search} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChevronArrow />
|
||||
</CommandItem>
|
||||
{pageSections.length > 0 && (
|
||||
<div className="border-l border-scale-500 ml-3 pt-3">
|
||||
{pageSections.map((section, i) => (
|
||||
<CommandItem
|
||||
forceMount
|
||||
className="ml-3 mb-3"
|
||||
onSelect={() => {
|
||||
// TODO: replace with Next.js router/Link when cross-project link logic solved
|
||||
window.location.assign(
|
||||
`/docs/${page.path}${page.type === PageType.Reference ? '/' : '#'}${
|
||||
section.slug
|
||||
}`
|
||||
)
|
||||
}}
|
||||
key={`${page.meta.title}__${section.heading}-item-index-${i}`}
|
||||
value={`${page.meta.title}__${section.heading}-item-index-${i}`}
|
||||
type="block-link"
|
||||
>
|
||||
<div className="grow flex gap-3 items-center">
|
||||
<IconContainer>
|
||||
<IconHash strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
|
||||
</IconContainer>
|
||||
<div className="flex flex-col gap-2">
|
||||
<cite>
|
||||
<TextHighlighter
|
||||
className="not-italic text-xs rounded-full px-2 py-1 bg-scale-500 text-scale-1200"
|
||||
text={page.meta.title}
|
||||
query={search}
|
||||
/>
|
||||
</cite>
|
||||
<CommandLabel>
|
||||
<TextHighlighter text={section.heading} query={search} />
|
||||
</CommandLabel>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronArrow />
|
||||
</CommandItem>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CommandGroup>
|
||||
)
|
||||
})}
|
||||
{!results && !hasSearchError && (
|
||||
<CommandGroup forceMount>
|
||||
{questions.map((question) => {
|
||||
const key = question.replace(/\s+/g, '_')
|
||||
return (
|
||||
<CommandItem
|
||||
disabled={isLoading}
|
||||
onSelect={() => {
|
||||
if (!search) {
|
||||
handleSearch(question)
|
||||
setSearch(question)
|
||||
}
|
||||
}}
|
||||
type="command"
|
||||
forceMount
|
||||
key={key}
|
||||
>
|
||||
<IconSearch />
|
||||
{question}
|
||||
</CommandItem>
|
||||
)
|
||||
})}
|
||||
</CommandGroup>
|
||||
)}
|
||||
{isLoading && !results && (
|
||||
<div className="p-6 grid gap-6 my-4">
|
||||
<p className="text-lg text-scale-900 text-center">Searching for results</p>
|
||||
</div>
|
||||
)}
|
||||
{results && results.length === 0 && (
|
||||
<div className="p-6 flex flex-col items-center gap-6 mt-4">
|
||||
<IconAlertTriangle strokeWidth={1.5} size={40} />
|
||||
<p className="text-lg text-center">No results found.</p>
|
||||
<Button size="tiny" type="secondary" onClick={handleResetPrompt}>
|
||||
Try again?
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{hasSearchError && (
|
||||
<div className="p-6 flex flex-col items-center gap-6 mt-4">
|
||||
<IconAlertTriangle strokeWidth={1.5} size={40} />
|
||||
<p className="text-lg text-center">
|
||||
Sorry, looks like we're having some issues with search!
|
||||
</p>
|
||||
<p className="text-sm text-center">Please try again in a bit.</p>
|
||||
<Button size="tiny" type="secondary" onClick={handleResetPrompt}>
|
||||
Try again?
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DocsSearch
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
import { cn } from '../../utils/cn'
|
||||
|
||||
export const LoadingLine = ({ loading }: { loading: boolean }) => {
|
||||
return (
|
||||
<div className="relative overflow-hidden w-full h-px bg-[#eceef0] dark:bg-[#2e2e2e] m-auto">
|
||||
<span
|
||||
className={cn(
|
||||
'absolute w-[80px] h-px ml-auto mr-auto left-0 right-0 text-center block top-0',
|
||||
'transition-all',
|
||||
'line-loading-bg',
|
||||
loading && 'animate-line-loading opacity-100',
|
||||
loading ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
></span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import * as React from 'react'
|
||||
import { ButtonHTMLAttributes, DetailedHTMLProps, PropsWithChildren, useRef } from 'react'
|
||||
import { useCommandMenu } from './CommandMenuProvider'
|
||||
|
||||
const SearchButton = ({
|
||||
children,
|
||||
...props
|
||||
}: PropsWithChildren<
|
||||
DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
|
||||
>) => {
|
||||
const searchButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const { setIsOpen } = useCommandMenu()
|
||||
|
||||
return (
|
||||
<button type="button" ref={searchButtonRef} onClick={() => setIsOpen(true)} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default SearchButton
|
||||
@@ -0,0 +1,11 @@
|
||||
import { useCommandState } from 'cmdk-supabase'
|
||||
import React from 'react'
|
||||
import { CommandItem } from './Command.utils'
|
||||
|
||||
export default function SearchOnlyItem({ children, isSubItem, ...props }: any) {
|
||||
const search = useCommandState((state) => state.search)
|
||||
// if search is empty & items is marked as a subItem, don't show it
|
||||
// ie: only show these items in search results, not top level
|
||||
if (!search && isSubItem) return null
|
||||
return <CommandItem {...props}>{children}</CommandItem>
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
import { CommandGroup } from './Command.utils'
|
||||
import SearchOnlyItem from './SearchOnlyItem'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import sharedItems from './utils/shared-nav-items.json'
|
||||
|
||||
export default function SearchableStudioItems() {
|
||||
const router = useRouter()
|
||||
return (
|
||||
<CommandGroup heading="Find">
|
||||
{sharedItems.tools
|
||||
.filter((item) => item.subItems) // filter items with subItems
|
||||
.flatMap((item) =>
|
||||
item.subItems?.map((subItem) => (
|
||||
<SearchOnlyItem
|
||||
key={subItem.url}
|
||||
isSubItem={true}
|
||||
onSelect={() => router.push(subItem.url)}
|
||||
>
|
||||
{subItem.label}
|
||||
</SearchOnlyItem>
|
||||
))
|
||||
)}
|
||||
</CommandGroup>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"label": "Home",
|
||||
"icon": "home",
|
||||
"url": "http://www.example.com/home",
|
||||
"sites": ["docs", "dashboard"],
|
||||
"subItems": [
|
||||
{
|
||||
"label": "About",
|
||||
"icon": "book",
|
||||
"url": "http://www.example.com/about",
|
||||
"sites": ["docs", "dashboard"]
|
||||
},
|
||||
{
|
||||
"label": "Services",
|
||||
"icon": "services",
|
||||
"url": "http://www.example.com/services",
|
||||
"sites": ["docs", "dashboard"]
|
||||
},
|
||||
{
|
||||
"label": "Contact",
|
||||
"icon": "contact",
|
||||
"url": "http://www.example.com/contact",
|
||||
"sites": ["docs", "dashboard"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Products",
|
||||
"icon": "products",
|
||||
"url": "http://www.example.com/products",
|
||||
"sites": ["dashboard"],
|
||||
"subItems": [
|
||||
{
|
||||
"label": "Mobile",
|
||||
"icon": "mobile",
|
||||
"url": "http://www.example.com/products/mobile"
|
||||
},
|
||||
{
|
||||
"label": "In person",
|
||||
"icon": "person",
|
||||
"url": "http://www.example.com/products/in-person"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"docsTools": [
|
||||
{
|
||||
"label": "Database",
|
||||
"url": "https://supabase.com/docs/guides/database"
|
||||
},
|
||||
{
|
||||
"label": "Auth",
|
||||
"url": "https://supabase.com/docs/guides/auth/overview"
|
||||
},
|
||||
{
|
||||
"label": "Edge Functions",
|
||||
"url": "https://supabase.com/docs/guides/functions"
|
||||
},
|
||||
{
|
||||
"label": "Realtime",
|
||||
"url": "https://supabase.com/docs/guides/realtime"
|
||||
},
|
||||
{
|
||||
"label": "Storage",
|
||||
"url": "https://supabase.com/docs/guides/storage"
|
||||
}
|
||||
],
|
||||
"docsSupport": [
|
||||
{
|
||||
"label": "Support",
|
||||
"url": "https://supabase.com/support"
|
||||
},
|
||||
{
|
||||
"label": "System Status",
|
||||
"url": "https://status.supabase.com/"
|
||||
},
|
||||
{
|
||||
"label": "Github Discussions",
|
||||
"url": "https://github.com/supabase/supabase/discussions"
|
||||
}
|
||||
],
|
||||
"docsGeneral": [
|
||||
{
|
||||
"icon": "arrowRight",
|
||||
"label": "Go to the Dashboard",
|
||||
"url": "https://app.supabase.io"
|
||||
},
|
||||
{
|
||||
"icon": "inbox",
|
||||
"label": "Read the Changelog",
|
||||
"url": "https://supabase.com/changelog"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export { default as CommandMenuProvider } from './CommandMenuProvider'
|
||||
export * from './CommandMenuProvider'
|
||||
|
||||
export { default as CommandMenu } from './CommandMenu'
|
||||
export * from './CommandMenu'
|
||||
|
||||
export { default as AiCommand } from './AiCommand'
|
||||
export * from './AiCommand'
|
||||
|
||||
export { default as DocsSearch } from './DocsSearch'
|
||||
export * from './DocsSearch'
|
||||
|
||||
export { default as SearchButton } from './SearchButton'
|
||||
export * from './SearchButton'
|
||||
|
||||
export * from './Command.icons'
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import * as React from 'react'
|
||||
import { IconArrowRight } from '../../Icon/icons/IconArrowRight'
|
||||
import dashboardItems from '../utils/dashboard-nav-items.json'
|
||||
import { CommandGroup, CommandItem, CommandLabel, CommandList } from '../Command.utils'
|
||||
import { useCommandMenu } from '../CommandMenuProvider'
|
||||
|
||||
export default function DashboardTableEditor() {
|
||||
const router = useRouter()
|
||||
const { setPages, currentPage, pages } = useCommandMenu()
|
||||
|
||||
return (
|
||||
<CommandGroup heading={dashboardItems.tools.label}>
|
||||
<CommandList>
|
||||
{!currentPage && (
|
||||
<>
|
||||
<CommandItem type="command" onSelect={() => setPages([...pages, 'projects'])}>
|
||||
Search projects…
|
||||
</CommandItem>
|
||||
<CommandItem type="command" onSelect={() => setPages([...pages, 'teams'])}>
|
||||
Join a team…
|
||||
</CommandItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
{currentPage === 'projects' && (
|
||||
<>
|
||||
<CommandItem type="link">Project B</CommandItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
{currentPage === 'teams' && (
|
||||
<>
|
||||
<CommandItem type="link">Team 1</CommandItem>
|
||||
<CommandItem type="link">Team 2</CommandItem>
|
||||
</>
|
||||
)}
|
||||
</CommandList>
|
||||
{/* {dashboardItems.tools.items.map((item) => (
|
||||
<CommandItem key={item.url} type="link" onSelect={() => router.push(item.url)}>
|
||||
<IconArrowRight className="text-scale-900" />
|
||||
<CommandLabel>
|
||||
Go to <span className="font-bold"> {item.label}</span>
|
||||
</CommandLabel>
|
||||
</CommandItem>
|
||||
))} */}
|
||||
</CommandGroup>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"tools": {
|
||||
"label": "Dashboard Tools",
|
||||
"items": [
|
||||
{
|
||||
"label": "dashboard - Database",
|
||||
"url": "https://supabase.com/docs/guides/database",
|
||||
"subItems": [
|
||||
{
|
||||
"label": "Triggers",
|
||||
"url": "http://www.example.com/triggers"
|
||||
},
|
||||
{
|
||||
"label": "Functions",
|
||||
"icon": "functions",
|
||||
"url": "http://www.example.com/functions"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "dashboard - Auth",
|
||||
"url": "https://supabase.com/docs/guides/auth/overview"
|
||||
},
|
||||
{
|
||||
"label": "dashboard - Edge Functions",
|
||||
"url": "https://supabase.com/docs/guides/functions"
|
||||
},
|
||||
{
|
||||
"label": "dashboard - Realtime",
|
||||
"url": "https://supabase.com/docs/guides/realtime"
|
||||
},
|
||||
{
|
||||
"label": "dashboard - Storage",
|
||||
"url": "https://supabase.com/docs/guides/storage"
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"label": "Dashboard Settings",
|
||||
"items": [
|
||||
{
|
||||
"label": "settings - Database general",
|
||||
"url": "https://supabase.com/docs/guides/database"
|
||||
},
|
||||
{
|
||||
"label": "settings - Database settings",
|
||||
"url": "https://supabase.com/docs/guides/auth/overview"
|
||||
},
|
||||
{
|
||||
"label": "settings - API settings",
|
||||
"url": "https://supabase.com/docs/guides/functions"
|
||||
},
|
||||
{
|
||||
"label": "settings - Auth settings",
|
||||
"url": "https://supabase.com/docs/guides/realtime"
|
||||
},
|
||||
{
|
||||
"label": "settings - Storage settings",
|
||||
"url": "https://supabase.com/docs/guides/storage"
|
||||
},
|
||||
{
|
||||
"label": "settings - subscription settings",
|
||||
"url": "https://supabase.com/docs/guides/storage"
|
||||
},
|
||||
{
|
||||
"label": "settings - usage settings",
|
||||
"url": "https://supabase.com/docs/guides/storage"
|
||||
},
|
||||
{
|
||||
"label": "settings - invoices settings",
|
||||
"url": "https://supabase.com/docs/guides/storage"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"label": "Home",
|
||||
"icon": "home",
|
||||
"url": "http://www.example.com/home",
|
||||
"sites": ["docs", "dashboard"],
|
||||
"subItems": [
|
||||
{
|
||||
"label": "About",
|
||||
"icon": "book",
|
||||
"url": "http://www.example.com/about",
|
||||
"sites": ["docs", "dashboard"]
|
||||
},
|
||||
{
|
||||
"label": "Services",
|
||||
"icon": "services",
|
||||
"url": "http://www.example.com/services",
|
||||
"sites": ["docs", "dashboard"]
|
||||
},
|
||||
{
|
||||
"label": "Contact",
|
||||
"icon": "contact",
|
||||
"url": "http://www.example.com/contact",
|
||||
"sites": ["docs", "dashboard"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Products",
|
||||
"icon": "products",
|
||||
"url": "http://www.example.com/products",
|
||||
"sites": ["dashboard"],
|
||||
"subItems": [
|
||||
{
|
||||
"label": "Mobile",
|
||||
"icon": "mobile",
|
||||
"url": "http://www.example.com/products/mobile"
|
||||
},
|
||||
{
|
||||
"label": "In person",
|
||||
"icon": "person",
|
||||
"url": "http://www.example.com/products/in-person"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"docsTools": [
|
||||
{
|
||||
"label": "Database",
|
||||
"url": "https://supabase.com/docs/guides/database"
|
||||
},
|
||||
{
|
||||
"label": "Auth",
|
||||
"url": "https://supabase.com/docs/guides/auth/overview"
|
||||
},
|
||||
{
|
||||
"label": "Edge Functions",
|
||||
"url": "https://supabase.com/docs/guides/functions"
|
||||
},
|
||||
{
|
||||
"label": "Realtime",
|
||||
"url": "https://supabase.com/docs/guides/realtime"
|
||||
},
|
||||
{
|
||||
"label": "Storage",
|
||||
"url": "https://supabase.com/docs/guides/storage"
|
||||
}
|
||||
],
|
||||
"docsSupport": [
|
||||
{
|
||||
"label": "Support",
|
||||
"url": "https://supabase.com/support"
|
||||
},
|
||||
{
|
||||
"label": "System Status",
|
||||
"url": "https://status.supabase.com/"
|
||||
},
|
||||
{
|
||||
"label": "Github Discussions",
|
||||
"url": "https://github.com/supabase/supabase/discussions"
|
||||
}
|
||||
],
|
||||
"docsGeneral": [
|
||||
{
|
||||
"icon": "arrowRight",
|
||||
"label": "Go to the Dashboard",
|
||||
"url": "https://app.supabase.io"
|
||||
},
|
||||
{
|
||||
"icon": "inbox",
|
||||
"label": "Read the Changelog",
|
||||
"url": "https://supabase.com/changelog"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
{
|
||||
"tools": [
|
||||
{
|
||||
"label": "Table Editor",
|
||||
"url": "https://app.supabase.com/project/_/editor"
|
||||
},
|
||||
{
|
||||
"label": "SQL Editor",
|
||||
"url": "https://app.supabase.com/project/_/sql"
|
||||
},
|
||||
{
|
||||
"label": "Database",
|
||||
"url": "https://app.supabase.com/project/_/database/tables",
|
||||
"subItems": [
|
||||
{
|
||||
"label": "Tables",
|
||||
"url": "https://app.supabase.com/project/_/database/tables"
|
||||
},
|
||||
{
|
||||
"label": "Triggers",
|
||||
"url": "https://app.supabase.com/project/_/database/triggers"
|
||||
},
|
||||
{
|
||||
"label": "Functions",
|
||||
"url": "https://app.supabase.com/project/_/database/functions"
|
||||
},
|
||||
{
|
||||
"label": "Extensions",
|
||||
"url": "https://app.supabase.com/project/_/database/extensions"
|
||||
},
|
||||
{
|
||||
"label": "Roles",
|
||||
"url": "https://app.supabase.com/project/_/database/roles"
|
||||
},
|
||||
{
|
||||
"label": "Replication",
|
||||
"url": "https://app.supabase.com/project/_/database/replication"
|
||||
},
|
||||
{
|
||||
"label": "Webhooks",
|
||||
"url": "https://app.supabase.com/project/_/database/webhooks"
|
||||
},
|
||||
{
|
||||
"label": "Backups",
|
||||
"url": "https://app.supabase.com/project/_/database/backups/scheduled"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Auth",
|
||||
"url": "https://app.supabase.com/project/_/auth/users",
|
||||
"subItems": [
|
||||
{
|
||||
"label": "Users",
|
||||
"url": "https://app.supabase.com/project/_/auth/users"
|
||||
},
|
||||
{
|
||||
"label": "Policies",
|
||||
"url": "https://app.supabase.com/project/_/auth/policies"
|
||||
},
|
||||
{
|
||||
"label": "Providers",
|
||||
"url": "https://app.supabase.com/project/_/auth/providers"
|
||||
},
|
||||
{
|
||||
"label": "Email Templates",
|
||||
"url": "https://app.supabase.com/project/_/auth/templates"
|
||||
},
|
||||
{
|
||||
"label": "URL Configuration",
|
||||
"url": "https://app.supabase.com/project/_/auth/url-configuration"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Storage",
|
||||
"url": "https://app.supabase.com/project/_/storage"
|
||||
},
|
||||
{
|
||||
"label": "Edge Functions",
|
||||
"url": "https://app.supabase.com/project/_/functions"
|
||||
},
|
||||
{
|
||||
"label": "Logs",
|
||||
"url": "https://app.supabase.com/project/_/logs/explorer",
|
||||
"subItems": [
|
||||
{
|
||||
"label": "Postgres logs",
|
||||
"url": "https://app.supabase.com/project/_/logs/postgres-logs"
|
||||
},
|
||||
{
|
||||
"label": "PostgREST logs",
|
||||
"url": "https://app.supabase.com/project/_/logs/postgrest-logs"
|
||||
},
|
||||
{
|
||||
"label": "Postgres logs",
|
||||
"url": "https://app.supabase.com/project/_/logs/pgbouncer-logs"
|
||||
},
|
||||
{
|
||||
"label": "Auth logs logs",
|
||||
"url": "https://app.supabase.com/project/_/logs/auth-logs"
|
||||
},
|
||||
{
|
||||
"label": "Storage logs",
|
||||
"url": "https://app.supabase.com/project/_/logs/storage-logs"
|
||||
},
|
||||
{
|
||||
"label": "realtime logs",
|
||||
"url": "https://app.supabase.com/project/_/logs/realtime-logs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "API Docs",
|
||||
"url": "https://app.supabase.com/project/_/api",
|
||||
"subItems": [
|
||||
{
|
||||
"label": "Auth docs",
|
||||
"url": "https://app.supabase.com/project/_/api?page=auth"
|
||||
},
|
||||
{
|
||||
"label": "User management docs",
|
||||
"url": "https://app.supabase.com/project/_/api?page=users"
|
||||
},
|
||||
{
|
||||
"label": "Graphql",
|
||||
"url": "https://app.supabase.com/project/_/api/graphiql"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Project Settings",
|
||||
"url": "https://app.supabase.com/project/_/settings/general",
|
||||
"subItems": [
|
||||
{
|
||||
"label": "General settings",
|
||||
"url": "https://app.supabase.com/project/_/settings/general"
|
||||
},
|
||||
{
|
||||
"label": "Database settings",
|
||||
"url": "https://app.supabase.com/project/_/settings/database"
|
||||
},
|
||||
{
|
||||
"label": "API settings",
|
||||
"url": "https://app.supabase.com/project/_/settings/api"
|
||||
},
|
||||
{
|
||||
"label": "Auth settings",
|
||||
"url": "https://app.supabase.com/project/_/settings/auth"
|
||||
},
|
||||
{
|
||||
"label": "Storage settings",
|
||||
"url": "https://app.supabase.com/project/_/settings/storage"
|
||||
},
|
||||
{
|
||||
"label": "Subscription settings",
|
||||
"url": "https://app.supabase.com/project/_/settings/billing/subscription"
|
||||
},
|
||||
{
|
||||
"label": "Project Usage",
|
||||
"url": "https://app.supabase.com/project/_/settings/billing/usage"
|
||||
},
|
||||
{
|
||||
"label": "Project Invoices",
|
||||
"url": "https://app.supabase.com/project/_/settings/billing/invoices"
|
||||
},
|
||||
{
|
||||
"label": "Project Invoices",
|
||||
"url": "https://app.supabase.com/project/_/settings/billing/invoices"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -43,7 +43,7 @@ function ContextMenu({
|
||||
</RadixContextMenu.Trigger>
|
||||
|
||||
<RadixContextMenu.Content
|
||||
sideOffset={alignOffset}
|
||||
alignOffset={alignOffset}
|
||||
// className={classes.join(' ')}
|
||||
style={style}
|
||||
>
|
||||
@@ -152,11 +152,7 @@ interface RadioGroupProps {
|
||||
onChange?(x: string): void
|
||||
}
|
||||
|
||||
export function RadioGroup({
|
||||
children,
|
||||
value: propsValue,
|
||||
onChange,
|
||||
}: RadioGroupProps) {
|
||||
export function RadioGroup({ children, value: propsValue, onChange }: RadioGroupProps) {
|
||||
const [value, setValue] = useState(propsValue ? propsValue : '')
|
||||
|
||||
const handleChange = (e: string) => {
|
||||
|
||||
@@ -3,23 +3,20 @@ import React, { useState } from 'react'
|
||||
import * as RadixDropdown from '@radix-ui/react-dropdown-menu'
|
||||
import { IconCheck } from '../Icon/icons/IconCheck'
|
||||
|
||||
// @ts-ignore
|
||||
// import DropdownStyles from './Dropdown.module.css'
|
||||
|
||||
import type * as RadixDropdownTypes from '@radix-ui/react-dropdown-menu/'
|
||||
|
||||
import styleHandler from '../../lib/theme/styleHandler'
|
||||
import { IconTarget } from '../Icon/icons/IconTarget'
|
||||
import { cn } from '../../utils/cn'
|
||||
|
||||
interface RootProps {
|
||||
interface RootProps extends RadixDropdownTypes.DropdownMenuProps {
|
||||
open?: boolean
|
||||
arrow?: boolean
|
||||
onOpenChange?: RadixDropdownTypes.DropdownMenuProps['onOpenChange'] // DropdownMenu['onOpenChange']
|
||||
side?: RadixDropdownTypes.DropdownMenuContentProps['side']
|
||||
align?: RadixDropdownTypes.DropdownMenuContentProps['align']
|
||||
sideOffset?: RadixDropdownTypes.DropdownMenuContentProps['sideOffset']
|
||||
overlay?: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
overlay?: RadixDropdownTypes.DropdownMenuContentProps['children']
|
||||
children?: RadixDropdownTypes.DropdownMenuTriggerProps['children']
|
||||
size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge' | 'content'
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
@@ -27,8 +24,6 @@ interface RootProps {
|
||||
}
|
||||
|
||||
function Dropdown({
|
||||
open,
|
||||
onOpenChange,
|
||||
align = 'center', //Default value
|
||||
side = 'bottom', //Default value
|
||||
sideOffset = 6,
|
||||
@@ -39,6 +34,7 @@ function Dropdown({
|
||||
style,
|
||||
arrow,
|
||||
isNested,
|
||||
...props
|
||||
}: RootProps) {
|
||||
let __styles = styleHandler('dropdown')
|
||||
|
||||
@@ -48,57 +44,63 @@ function Dropdown({
|
||||
}
|
||||
|
||||
return (
|
||||
<RadixDropdown.Root onOpenChange={onOpenChange} open={open}>
|
||||
<RadixDropdown.Root {...props}>
|
||||
{isNested ? (
|
||||
<RadixDropdown.TriggerItem className={[__styles.item_nested].join(' ')}>
|
||||
{children}
|
||||
</RadixDropdown.TriggerItem>
|
||||
<RadixDropdown.Sub>
|
||||
<RadixDropdown.SubTrigger className={[__styles.item_nested].join(' ')}>
|
||||
{children}
|
||||
</RadixDropdown.SubTrigger>
|
||||
<RadixDropdown.SubContent
|
||||
sideOffset={sideOffset}
|
||||
className={classes.join(' ')}
|
||||
style={style}
|
||||
>
|
||||
{arrow && (
|
||||
<RadixDropdown.Arrow className={__styles.arrow} offset={10}></RadixDropdown.Arrow>
|
||||
)}
|
||||
{overlay}
|
||||
</RadixDropdown.SubContent>
|
||||
</RadixDropdown.Sub>
|
||||
) : (
|
||||
<RadixDropdown.Trigger className={__styles.trigger}>{children}</RadixDropdown.Trigger>
|
||||
<>
|
||||
<RadixDropdown.Trigger className={__styles.trigger}>{children}</RadixDropdown.Trigger>
|
||||
<RadixDropdown.Portal>
|
||||
<RadixDropdown.Content
|
||||
sideOffset={sideOffset}
|
||||
side={side}
|
||||
align={align}
|
||||
className={classes.join(' ')}
|
||||
style={style}
|
||||
>
|
||||
{arrow && (
|
||||
<RadixDropdown.Arrow className={__styles.arrow} offset={10}></RadixDropdown.Arrow>
|
||||
)}
|
||||
{overlay}
|
||||
</RadixDropdown.Content>
|
||||
</RadixDropdown.Portal>
|
||||
</>
|
||||
)}
|
||||
|
||||
<RadixDropdown.Content
|
||||
portalled={true}
|
||||
sideOffset={sideOffset}
|
||||
side={side}
|
||||
align={align}
|
||||
className={classes.join(' ')}
|
||||
style={style}
|
||||
>
|
||||
{arrow && (
|
||||
<RadixDropdown.Arrow className={__styles.arrow} offset={10}></RadixDropdown.Arrow>
|
||||
)}
|
||||
{overlay}
|
||||
</RadixDropdown.Content>
|
||||
</RadixDropdown.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export function RightSlot({ children }: any) {
|
||||
export function RightSlot({ children }: { children: React.ReactNode }) {
|
||||
let __styles = styleHandler('dropdown')
|
||||
return <div className={__styles.right_slot}>{children}</div>
|
||||
}
|
||||
|
||||
interface ItemProps {
|
||||
className?: string
|
||||
interface ItemProps extends RadixDropdownTypes.DropdownMenuItemProps {
|
||||
children: React.ReactNode
|
||||
icon?: React.ReactNode
|
||||
disabled?: boolean
|
||||
onClick?: (event: Event) => void
|
||||
rightSlot?: React.ReactNode
|
||||
}
|
||||
|
||||
export function Item({ className, children, icon, disabled, onClick, rightSlot }: ItemProps) {
|
||||
export function Item({ icon, className, ...props }: ItemProps) {
|
||||
let __styles = styleHandler('dropdown')
|
||||
|
||||
return (
|
||||
<RadixDropdown.Item
|
||||
className={`${__styles.item} ${className}`}
|
||||
disabled={disabled}
|
||||
onSelect={onClick}
|
||||
>
|
||||
<RadixDropdown.Item className={cn(__styles.item, className)} {...props}>
|
||||
{icon && icon}
|
||||
<span>{children}</span>
|
||||
<span>{props.children}</span>
|
||||
</RadixDropdown.Item>
|
||||
)
|
||||
}
|
||||
@@ -123,33 +125,33 @@ export function Misc({ children, icon }: ItemProps) {
|
||||
)
|
||||
}
|
||||
|
||||
interface CheckboxProps {
|
||||
children: React.ReactNode
|
||||
checked?: boolean
|
||||
onChange?(x: boolean): void
|
||||
disabled?: boolean
|
||||
ItemIndicator?: React.ReactNode
|
||||
}
|
||||
|
||||
export function Separator() {
|
||||
let __styles = styleHandler('dropdown')
|
||||
|
||||
return <RadixDropdown.Separator className={__styles.separator} />
|
||||
}
|
||||
|
||||
// to do : remove onChange omit in favor of using onCheckedChange
|
||||
interface CheckboxProps extends Omit<RadixDropdownTypes.DropdownMenuCheckboxItemProps, 'onChange'> {
|
||||
ItemIndicator?: React.ReactNode
|
||||
onChange?(x: boolean): void
|
||||
}
|
||||
|
||||
export function Checkbox({
|
||||
children,
|
||||
checked: propsChecked,
|
||||
onChange,
|
||||
disabled,
|
||||
ItemIndicator,
|
||||
onChange,
|
||||
className,
|
||||
...props
|
||||
}: CheckboxProps) {
|
||||
const [checked, setChecked] = useState(propsChecked ? propsChecked : false)
|
||||
const [checked, setChecked] = useState(propsChecked ?? false)
|
||||
|
||||
let __styles = styleHandler('dropdown')
|
||||
|
||||
const handleChange = (e: boolean) => {
|
||||
const handleChange = (e: any) => {
|
||||
// to do : remove onChange in favor of using onCheckedChange
|
||||
if (onChange) onChange(e)
|
||||
if (props.onCheckedChange) props.onCheckedChange(e)
|
||||
setChecked(e)
|
||||
}
|
||||
|
||||
@@ -157,71 +159,59 @@ export function Checkbox({
|
||||
<RadixDropdown.CheckboxItem
|
||||
checked={checked}
|
||||
onCheckedChange={handleChange}
|
||||
className={`${__styles.item} ${__styles.input}`}
|
||||
disabled={disabled}
|
||||
className={cn(__styles.item, __styles.input, className)}
|
||||
{...props}
|
||||
>
|
||||
<RadixDropdown.ItemIndicator className={__styles.check}>
|
||||
{ItemIndicator ? ItemIndicator : <IconCheck size="tiny" strokeWidth={3} />}
|
||||
<RadixDropdown.CheckboxItem />
|
||||
</RadixDropdown.ItemIndicator>
|
||||
<span>{children}</span>
|
||||
<span>{props.children}</span>
|
||||
</RadixDropdown.CheckboxItem>
|
||||
)
|
||||
}
|
||||
|
||||
interface RadioProps {
|
||||
children: React.ReactNode
|
||||
value: string
|
||||
interface RadioProps extends RadixDropdownTypes.DropdownMenuRadioItemProps {
|
||||
ItemIndicator?: React.ReactNode
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export function Radio({ children, value, ItemIndicator, disabled }: RadioProps) {
|
||||
export function Radio({ ItemIndicator, className, ...props }: RadioProps) {
|
||||
let __styles = styleHandler('dropdown')
|
||||
|
||||
return (
|
||||
<RadixDropdown.RadioItem
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
className={`${__styles.item} ${__styles.input}`}
|
||||
>
|
||||
<RadixDropdown.RadioItem className={cn(__styles.item, __styles.input, className)} {...props}>
|
||||
<RadixDropdown.ItemIndicator className={__styles.check}>
|
||||
{ItemIndicator ? ItemIndicator : <IconTarget strokeWidth={6} size={10} />}
|
||||
</RadixDropdown.ItemIndicator>
|
||||
<span>{children}</span>
|
||||
<span>{props.children}</span>
|
||||
</RadixDropdown.RadioItem>
|
||||
)
|
||||
}
|
||||
|
||||
interface RadioGroupProps {
|
||||
children: React.ReactNode
|
||||
value: string
|
||||
// to do : remove onChange omit in favor of using onValueChange
|
||||
interface RadioGroupProps extends Omit<RadixDropdownTypes.DropdownMenuRadioGroupProps, 'onChange'> {
|
||||
onChange?(x: string): void
|
||||
}
|
||||
|
||||
export function RadioGroup({ children, value: propsValue, onChange }: RadioGroupProps) {
|
||||
export function RadioGroup({ value: propsValue, onChange, ...props }: RadioGroupProps) {
|
||||
const [value, setValue] = useState(propsValue ? propsValue : '')
|
||||
|
||||
const handleChange = (e: string) => {
|
||||
const handleChange = (e: any) => {
|
||||
// to do : remove onChange in favor of using onValueChange
|
||||
if (onChange) onChange(e)
|
||||
if (props.onValueChange) props.onValueChange(e)
|
||||
setValue(e)
|
||||
}
|
||||
|
||||
return (
|
||||
<RadixDropdown.RadioGroup value={value} onValueChange={handleChange}>
|
||||
{children}
|
||||
</RadixDropdown.RadioGroup>
|
||||
)
|
||||
return <RadixDropdown.RadioGroup value={value} onValueChange={handleChange} {...props} />
|
||||
}
|
||||
|
||||
interface LabelProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
interface LabelProps extends RadixDropdownTypes.DropdownMenuLabelProps {}
|
||||
|
||||
export function Label({ children }: LabelProps) {
|
||||
export function Label({ className, ...props }: LabelProps) {
|
||||
let __styles = styleHandler('dropdown')
|
||||
|
||||
return <RadixDropdown.Label className={__styles.label}>{children}</RadixDropdown.Label>
|
||||
return <RadixDropdown.Label className={cn(__styles.label, className)} {...props} />
|
||||
}
|
||||
|
||||
Dropdown.Item = Item
|
||||
|
||||
@@ -4,7 +4,7 @@ interface ContextProps {
|
||||
formContextOnChange: any
|
||||
values: any
|
||||
errors: any
|
||||
handleBlur: React.FocusEventHandler | null
|
||||
handleBlur: ((e: React.FocusEvent<any, any>) => void) | null
|
||||
touched: any
|
||||
fieldLevelValidation: any
|
||||
}
|
||||
@@ -25,14 +25,7 @@ const FormContext = createContext<ContextProps>({
|
||||
})
|
||||
|
||||
export const FormContextProvider = (props: Provider) => {
|
||||
const {
|
||||
formContextOnChange,
|
||||
values,
|
||||
errors,
|
||||
handleBlur,
|
||||
touched,
|
||||
fieldLevelValidation,
|
||||
} = props
|
||||
const { formContextOnChange, values, errors, handleBlur, touched, fieldLevelValidation } = props
|
||||
|
||||
const value = {
|
||||
formContextOnChange: formContextOnChange,
|
||||
@@ -43,18 +36,14 @@ export const FormContextProvider = (props: Provider) => {
|
||||
fieldLevelValidation: fieldLevelValidation,
|
||||
}
|
||||
|
||||
return (
|
||||
<FormContext.Provider value={value}>{props.children}</FormContext.Provider>
|
||||
)
|
||||
return <FormContext.Provider value={value}>{props.children}</FormContext.Provider>
|
||||
}
|
||||
|
||||
// context helper to avoid using a consumer component
|
||||
export const useFormContext = () => {
|
||||
const context = useContext(FormContext)
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
`useFormContextOnChange must be used within a FormContextProvider.`
|
||||
)
|
||||
throw new Error(`useFormContextOnChange must be used within a FormContextProvider.`)
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -159,8 +159,9 @@ const Modal = ({
|
||||
<Dialog.Overlay className={__styles.overlay} />
|
||||
<Dialog.Overlay className={__styles.scroll_overlay}>
|
||||
<Dialog.Content
|
||||
className={[__styles.base, __styles.size[size]].join(' ')}
|
||||
className={[__styles.base, __styles.size[size], className].join(' ')}
|
||||
onInteractOutside={props.onInteractOutside}
|
||||
onEscapeKeyDown={props.onEscapeKeyDown}
|
||||
>
|
||||
{header && <div className={__styles.header}>{header}</div>}
|
||||
{/* <div
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { Button } from '../Button'
|
||||
import { Divider } from '../Divider'
|
||||
import { IconChevronDown } from '../../index'
|
||||
import Typography from '../Typography'
|
||||
import { IconChevronDown } from '../Icon/icons/IconChevronDown'
|
||||
|
||||
import { Popover } from '.'
|
||||
import { IconLogIn } from '../Icon/icons/IconLogIn'
|
||||
import { Input } from '../Input'
|
||||
|
||||
export default {
|
||||
@@ -19,7 +16,6 @@ export const Default = (args: any) => (
|
||||
side="bottom"
|
||||
align="end"
|
||||
size="content"
|
||||
portalled
|
||||
showClose
|
||||
header={
|
||||
<div className="flex justify-between items-center">
|
||||
|
||||
@@ -16,7 +16,6 @@ interface RootProps {
|
||||
onOpenChange?: RadixPopoverTypes.PopoverProps['onOpenChange']
|
||||
open?: boolean
|
||||
overlay?: React.ReactNode
|
||||
portalled?: boolean
|
||||
showClose?: boolean
|
||||
side?: RadixPopoverTypes.PopoverContentProps['side']
|
||||
sideOffset?: RadixPopoverTypes.PopoverContentProps['sideOffset']
|
||||
@@ -37,7 +36,6 @@ function Popover({
|
||||
onOpenChange,
|
||||
open,
|
||||
overlay,
|
||||
portalled,
|
||||
side = 'bottom',
|
||||
sideOffset = 6,
|
||||
style,
|
||||
@@ -67,24 +65,25 @@ function Popover({
|
||||
{children}
|
||||
</RadixPopover.Trigger>
|
||||
|
||||
<RadixPopover.Content
|
||||
sideOffset={sideOffset}
|
||||
side={side}
|
||||
align={align}
|
||||
className={classes.join(' ')}
|
||||
style={style}
|
||||
portalled={portalled}
|
||||
>
|
||||
{arrow && (
|
||||
<RadixPopover.Arrow
|
||||
// className={DropdownStyles['sbui-popover__arrow']}
|
||||
offset={10}
|
||||
></RadixPopover.Arrow>
|
||||
)}
|
||||
{header && <div className={__styles.header}>{header}</div>}
|
||||
{overlay}
|
||||
{footer && <div className={__styles.footer}>{footer}</div>}
|
||||
</RadixPopover.Content>
|
||||
<RadixPopover.Portal>
|
||||
<RadixPopover.Content
|
||||
sideOffset={sideOffset}
|
||||
side={side}
|
||||
align={align}
|
||||
className={classes.join(' ')}
|
||||
style={style}
|
||||
>
|
||||
{arrow && (
|
||||
<RadixPopover.Arrow
|
||||
// className={DropdownStyles['sbui-popover__arrow']}
|
||||
offset={10}
|
||||
></RadixPopover.Arrow>
|
||||
)}
|
||||
{header && <div className={__styles.header}>{header}</div>}
|
||||
{overlay}
|
||||
{footer && <div className={__styles.footer}>{footer}</div>}
|
||||
</RadixPopover.Content>
|
||||
</RadixPopover.Portal>
|
||||
</RadixPopover.Root>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -972,6 +972,7 @@ export default {
|
||||
|
||||
sidepanel: {
|
||||
base: `
|
||||
z-50
|
||||
bg-scale-100 dark:bg-scale-300
|
||||
flex flex-col
|
||||
fixed
|
||||
@@ -1023,6 +1024,7 @@ export default {
|
||||
bg-scale-300 dark:bg-scale-500
|
||||
`,
|
||||
overlay: `
|
||||
z-50
|
||||
fixed
|
||||
bg-scale-300
|
||||
dark:bg-scale-100
|
||||
@@ -1210,12 +1212,12 @@ export default {
|
||||
data-open:text-scale-1200
|
||||
`,
|
||||
content: `
|
||||
z-50
|
||||
bg-scale-100 dark:bg-scale-300
|
||||
border border-scale-300 dark:border-scale-500
|
||||
rounded
|
||||
shadow-lg
|
||||
py-1.5
|
||||
|
||||
origin-dropdown
|
||||
data-open:animate-dropdown-content-show
|
||||
data-closed:animate-dropdown-content-hide
|
||||
@@ -1297,6 +1299,7 @@ export default {
|
||||
|
||||
`,
|
||||
content: `
|
||||
z-50
|
||||
bg-scale-100 dark:bg-scale-300
|
||||
border border-scale-300 dark:border-scale-500
|
||||
rounded
|
||||
@@ -1469,6 +1472,7 @@ export default {
|
||||
xxxlarge: `sm:align-middle sm:w-full max-w-7xl`,
|
||||
},
|
||||
overlay: `
|
||||
z-50
|
||||
fixed
|
||||
bg-scale-300
|
||||
dark:bg-scale-100
|
||||
@@ -1480,13 +1484,13 @@ export default {
|
||||
data-open:animate-fade-in-overlay-bg
|
||||
`,
|
||||
scroll_overlay: `
|
||||
z-50
|
||||
fixed
|
||||
inset-0
|
||||
grid
|
||||
place-items-center
|
||||
overflow-y-auto
|
||||
data-open:animate-overlay-show data-closed:animate-overlay-hide
|
||||
z-50
|
||||
`,
|
||||
separator: `
|
||||
w-full
|
||||
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
declare module 'sse.js' {
|
||||
export type SSEOptions = EventSourceInit & {
|
||||
headers?: Record<string, string>
|
||||
payload?: string
|
||||
method?: string
|
||||
}
|
||||
|
||||
export class SSE extends EventSource {
|
||||
constructor(url: string | URL, sseOptions?: SSEOptions)
|
||||
stream(): void
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "tsconfig/react-library.json",
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"."
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"build",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -104,17 +104,19 @@ export function JsonEditor<TRow, TSummaryRow = unknown>({
|
||||
<IconMaximize size={12} strokeWidth={2} />
|
||||
</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Expand editor</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Expand editor</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</BlockKeys>
|
||||
|
||||
@@ -52,17 +52,21 @@ export const ForeignKeyFormatter = (props: PropsWithChildren<FormatterProps<Supa
|
||||
</a>
|
||||
</Link>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">View referencing record</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">View referencing record</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -130,17 +130,19 @@ export function ColumnHeader<R>({
|
||||
<IconKey size="tiny" strokeWidth={2} />
|
||||
</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Primary key</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Primary key</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
<span className="sb-grid-column-header__inner__name" title={hoverValue}>
|
||||
@@ -152,17 +154,19 @@ export function ColumnHeader<R>({
|
||||
<Tooltip.Trigger>
|
||||
<IconLock size="tiny" strokeWidth={2} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Encrypted column</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Encrypted column</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
@@ -184,32 +188,34 @@ function renderColumnIcon(
|
||||
<Tooltip.Trigger>
|
||||
<IconLink size="tiny" strokeWidth={2} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<div>
|
||||
<p className="text-xs text-scale-1100">Foreign key relation:</p>
|
||||
<div className="flex items-center space-x-1">
|
||||
<p className="text-xs text-scale-1200">{name}</p>
|
||||
<IconArrowRight size="tiny" strokeWidth={1.5} />
|
||||
<p className="text-xs text-scale-1200">
|
||||
{foreignKey?.targetTableSchema}.{foreignKey?.targetTableName}.
|
||||
{foreignKey?.targetColumnName}
|
||||
</p>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<div>
|
||||
<p className="text-xs text-scale-1100">Foreign key relation:</p>
|
||||
<div className="flex items-center space-x-1">
|
||||
<p className="text-xs text-scale-1200">{name}</p>
|
||||
<IconArrowRight size="tiny" strokeWidth={1.5} />
|
||||
<p className="text-xs text-scale-1200">
|
||||
{foreignKey?.targetTableSchema}.{foreignKey?.targetTableName}.
|
||||
{foreignKey?.targetColumnName}
|
||||
</p>
|
||||
</div>
|
||||
{foreignKey?.deletionAction !== FOREIGN_KEY_DELETION_ACTION.NO_ACTION && (
|
||||
<p className="text-xs text-scale-1200 mt-1">
|
||||
On delete: {getForeignKeyDeletionAction(foreignKey?.deletionAction)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{foreignKey?.deletionAction !== FOREIGN_KEY_DELETION_ACTION.NO_ACTION && (
|
||||
<p className="text-xs text-scale-1200 mt-1">
|
||||
On delete: {getForeignKeyDeletionAction(foreignKey?.deletionAction)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)
|
||||
default:
|
||||
|
||||
@@ -55,19 +55,21 @@ const ColumnMenu: React.FC<ColumnMenuProps> = ({ column, isEncrypted }) => {
|
||||
</Dropdown.Item>
|
||||
</Tooltip.Trigger>
|
||||
{isEncrypted && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
Encrypted columns cannot be edited
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
Encrypted columns cannot be edited
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
|
||||
@@ -169,19 +169,21 @@ const ProviderForm: FC<Props> = ({ provider }) => {
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateConfig && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update provider settings
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update provider settings
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
@@ -43,28 +43,31 @@ const PolicyDefinition: FC<Props> = ({
|
||||
<Tooltip.Trigger>
|
||||
<IconHelpCircle className="text-scale-1100" size={16} strokeWidth={1.5} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'w-[300px] space-y-2 border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<p className="text-xs text-scale-1200">
|
||||
This expression will be added to queries that refer to the table if row-level
|
||||
security is enabled.
|
||||
</p>
|
||||
<p className="text-xs text-scale-1200">
|
||||
Rows for which the expression returns true will be visible. Any rows for which
|
||||
the expression returns false or null will not be visible to the user (in a
|
||||
SELECT), and will not be available for modification (in an UPDATE or DELETE).
|
||||
</p>
|
||||
<p className="text-xs text-scale-1200">
|
||||
Such rows are silently suppressed - no error is reported.
|
||||
</p>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'w-[300px] space-y-2 border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<p className="text-xs text-scale-1200">
|
||||
This expression will be added to queries that refer to the table if
|
||||
row-level security is enabled.
|
||||
</p>
|
||||
<p className="text-xs text-scale-1200">
|
||||
Rows for which the expression returns true will be visible. Any rows for
|
||||
which the expression returns false or null will not be visible to the user
|
||||
(in a SELECT), and will not be available for modification (in an UPDATE or
|
||||
DELETE).
|
||||
</p>
|
||||
<p className="text-xs text-scale-1200">
|
||||
Such rows are silently suppressed - no error is reported.
|
||||
</p>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
<p className="text-sm text-scale-900">
|
||||
@@ -87,29 +90,31 @@ const PolicyDefinition: FC<Props> = ({
|
||||
<Tooltip.Trigger>
|
||||
<IconHelpCircle className="text-scale-1100" size={16} strokeWidth={1.5} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'w-[300px] space-y-2 border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<p className="text-xs text-scale-1200">
|
||||
This expression will be used in INSERT and UPDATE queries against the table if
|
||||
row-level security is enabled.
|
||||
</p>
|
||||
<p className="text-xs text-scale-1200">
|
||||
Only rows for which the expression evaluates to true will be allowed. An error
|
||||
will be thrown if the expression evaluates to false or null for any of the
|
||||
records inserted or any of the records that result from the update.
|
||||
</p>
|
||||
<p className="text-xs text-scale-1200">
|
||||
Note that this expression is evaluated against the proposed new contents of
|
||||
the row, not the original contents.
|
||||
</p>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'w-[300px] space-y-2 border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<p className="text-xs text-scale-1200">
|
||||
This expression will be used in INSERT and UPDATE queries against the table
|
||||
if row-level security is enabled.
|
||||
</p>
|
||||
<p className="text-xs text-scale-1200">
|
||||
Only rows for which the expression evaluates to true will be allowed. An
|
||||
error will be thrown if the expression evaluates to false or null for any of
|
||||
the records inserted or any of the records that result from the update.
|
||||
</p>
|
||||
<p className="text-xs text-scale-1200">
|
||||
Note that this expression is evaluated against the proposed new contents of
|
||||
the row, not the original contents.
|
||||
</p>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
<p className="text-sm text-scale-900">
|
||||
|
||||
@@ -82,19 +82,21 @@ const PolicyRow: FC<Props> = ({
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdatePolicies && (
|
||||
<Tooltip.Content side="left">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to edit RLS policies
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="left">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to edit RLS policies
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
|
||||
+30
-26
@@ -52,19 +52,21 @@ const PolicyTableRowHeader: FC<Props> = ({
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canCreatePolicies && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to create RLS policies
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to create RLS policies
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
@@ -78,19 +80,21 @@ const PolicyTableRowHeader: FC<Props> = ({
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canToggleRLS && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to toggle RLS
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to toggle RLS
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
@@ -97,19 +97,21 @@ const RedirectDomains = () => {
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateConfig && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update redirect URLs
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update redirect URLs
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
@@ -47,19 +47,21 @@ const AddUserDropdown = ({ projectKpsVersion }: AddUserDropdownProps) => {
|
||||
</Dropdown.Item>
|
||||
</Tooltip.Trigger>
|
||||
{!canInviteUsers && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to invite users
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to invite users
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
@@ -76,19 +78,21 @@ const AddUserDropdown = ({ projectKpsVersion }: AddUserDropdownProps) => {
|
||||
</Tooltip.Trigger>
|
||||
|
||||
{!canCreateUsers && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to create users
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to create users
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</>
|
||||
|
||||
@@ -184,19 +184,21 @@ const UserDropdown: FC<Props> = ({ user, canRemoveUser, canRemoveMFAFactors }) =
|
||||
need to double check with KM / anyone familiar with ABAC
|
||||
*/}
|
||||
{!canRemoveMFAFactors && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to remove a user's authentication factors.
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to remove a user's authentication factors.
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
@@ -210,19 +212,21 @@ const UserDropdown: FC<Props> = ({ user, canRemoveUser, canRemoveMFAFactors }) =
|
||||
</Dropdown.Item>
|
||||
</Tooltip.Trigger>
|
||||
{!canRemoveUser && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to delete users
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to delete users
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</>
|
||||
|
||||
@@ -48,41 +48,43 @@ const InvoiceStatusBadge: FC<Props> = ({ status }) => {
|
||||
{statusMapping?.label || status}
|
||||
</Badge>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'w-[300px] space-y-2 border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
{[InvoiceStatus.OPEN, InvoiceStatus.UNCOLLECTIBLE].includes(status) && (
|
||||
<p className="text-xs text-scale-1200">
|
||||
We were not able to collect the money. Make sure you have a valid payment method and
|
||||
enough funds. Outstanding invoices may cause restrictions. You can manually pay the
|
||||
using the "Pay Now" button.
|
||||
</p>
|
||||
)}
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'w-[300px] space-y-2 border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
{[InvoiceStatus.OPEN, InvoiceStatus.UNCOLLECTIBLE].includes(status) && (
|
||||
<p className="text-xs text-scale-1200">
|
||||
We were not able to collect the money. Make sure you have a valid payment method and
|
||||
enough funds. Outstanding invoices may cause restrictions. You can manually pay the
|
||||
using the "Pay Now" button.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{status === InvoiceStatus.DRAFT && (
|
||||
<p className="text-xs text-scale-1200">
|
||||
The invoice will soon be finalized and charged for.
|
||||
</p>
|
||||
)}
|
||||
{status === InvoiceStatus.DRAFT && (
|
||||
<p className="text-xs text-scale-1200">
|
||||
The invoice will soon be finalized and charged for.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{status === InvoiceStatus.PAID && (
|
||||
<p className="text-xs text-scale-1200">
|
||||
The invoice has been paid successfully. No action is required on your side.
|
||||
</p>
|
||||
)}
|
||||
{status === InvoiceStatus.PAID && (
|
||||
<p className="text-xs text-scale-1200">
|
||||
The invoice has been paid successfully. No action is required on your side.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{status === InvoiceStatus.VOID && (
|
||||
<p className="text-xs text-scale-1200">
|
||||
This invoice has been forgiven. No action is required on your side.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
{status === InvoiceStatus.VOID && (
|
||||
<p className="text-xs text-scale-1200">
|
||||
This invoice has been forgiven. No action is required on your side.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -372,20 +372,22 @@ const PaymentSummaryPanel: FC<Props> = ({
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdatePaymentMethods && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'w-48 border border-scale-200 text-center', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to add new payment methods to this
|
||||
organization
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'w-48 border border-scale-200 text-center', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to add new payment methods to this
|
||||
organization
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
@@ -448,31 +450,35 @@ const PaymentSummaryPanel: FC<Props> = ({
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!hasChangesToPlan ? (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
No changes made to your subscription
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
No changes made to your subscription
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
) : !selectedPaymentMethod ? (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Please select a payment method</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Please select a payment method</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
@@ -54,17 +54,19 @@ const PaymentTotal: FC<Props> = ({
|
||||
onClick={() => setShowCostBreakdown(true)}
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">How is this calculated?</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">How is this calculated?</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -112,19 +112,23 @@ const PlanCTAButton: FC<Props> = ({ plan, currentPlan, onSelectPlan }) => {
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!disabled && projectUpdateDisabled && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200 w-[260px] flex items-center justify-center',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200 text-center">
|
||||
Subscription changes are currently disabled, our engineers are working on a fix
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200 w-[260px] flex items-center justify-center',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200 text-center">
|
||||
Subscription changes are currently disabled, our engineers are working on a fix
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
@@ -55,20 +55,24 @@ const SpendCapModal: FC<Props> = ({ visible, onHide }) => {
|
||||
className="transition opacity-50 cursor-pointer hover:opacity-100"
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
Monthly Active Users: A user that has made an API request in the last
|
||||
month
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
Monthly Active Users: A user that has made an API request in the
|
||||
last month
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
<p className="w-[25%] text-sm">100,000</p>
|
||||
|
||||
@@ -97,29 +97,31 @@ const Subscription: FC<Props> = ({
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateSubscription || projectUpdateDisabled ? (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'border border-scale-200 text-center', //border
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
{projectUpdateDisabled ? (
|
||||
<>
|
||||
Subscription changes are currently disabled.
|
||||
<br />
|
||||
Our engineers are working on a fix.
|
||||
</>
|
||||
) : !canUpdateSubscription ? (
|
||||
'You need additional permissions to amend subscriptions'
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'border border-scale-200 text-center', //border
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
{projectUpdateDisabled ? (
|
||||
<>
|
||||
Subscription changes are currently disabled.
|
||||
<br />
|
||||
Our engineers are working on a fix.
|
||||
</>
|
||||
) : !canUpdateSubscription ? (
|
||||
'You need additional permissions to amend subscriptions'
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
@@ -34,19 +34,21 @@ const PITRNotice = ({}) => {
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateSubscription && (
|
||||
<Tooltip.Content side="left">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to amend subscriptions
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="left">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to amend subscriptions
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
@@ -192,19 +192,21 @@ const PITRSelection = ({}) => {
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{isSelectedOutOfRange && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
|
||||
'border-scale-200 border w-48 text-center',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-scale-1200 text-xs">
|
||||
Selected date is out of range where backups are available
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
|
||||
'border-scale-200 border w-48 text-center',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-scale-1200 text-xs">
|
||||
Selected date is out of range where backups are available
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
@@ -55,19 +55,21 @@ const PITRStatus: FC<Props> = ({ selectedTimezone, onUpdateTimezone, onSetConfig
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canTriggerPhysicalBackup && (
|
||||
<Tooltip.Content side="left">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to trigger a PITR recovery
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="left">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to trigger a PITR recovery
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
@@ -110,17 +110,19 @@ const TimeInput: FC<Props> = ({ defaultTime, minimumTime, maximumTime, onChange
|
||||
className="w-full text-sm bg-transparent p-0 text-center outline-none border-none focus:ring-0"
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
|
||||
'border-scale-200 border',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-scale-1200 text-xs">Hours (HH)</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
|
||||
'border-scale-200 border',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-scale-1200 text-xs">Hours (HH)</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
<span>:</span>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
@@ -138,17 +140,19 @@ const TimeInput: FC<Props> = ({ defaultTime, minimumTime, maximumTime, onChange
|
||||
className="w-full text-sm bg-transparent p-0 text-center outline-none border-none focus:ring-0"
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
|
||||
'border-scale-200 border',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-scale-1200 text-xs">Minutes (MM)</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
|
||||
'border-scale-200 border',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-scale-1200 text-xs">Minutes (MM)</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
<span>:</span>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
@@ -166,17 +170,19 @@ const TimeInput: FC<Props> = ({ defaultTime, minimumTime, maximumTime, onChange
|
||||
className="w-full text-sm bg-transparent p-0 text-center outline-none border-none focus:ring-0"
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
|
||||
'border-scale-200 border',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-scale-1200 text-xs">Seconds (SS)</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
|
||||
'border-scale-200 border',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-scale-1200 text-xs">Seconds (SS)</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
{error && <p className="text-sm text-red-900">{error}</p>}
|
||||
|
||||
@@ -89,19 +89,21 @@ const FunctionList: FC<Props> = ({
|
||||
<Tooltip.Trigger>
|
||||
<Button as="span" disabled type="default" icon={<IconMoreVertical />} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="left">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update functions
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="left">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update functions
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -90,19 +90,23 @@ const FunctionsList: FC<Props> = ({
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canCreateFunctions && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to create functions
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to create functions
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
@@ -105,19 +105,21 @@ const HookList: FC<Props> = ({
|
||||
<Tooltip.Trigger>
|
||||
<Button as="span" disabled type="default" icon={<IconMoreVertical />} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="left">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update webhooks
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="left">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update webhooks
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -107,19 +107,21 @@ const HooksList = ({
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canCreateWebhooks && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to create webhooks
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to create webhooks
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
@@ -184,19 +184,21 @@ const RoleRow: FC<Props> = ({ role, disabled = false, onSelectDelete }) => {
|
||||
className="ml-2 relative top-[3px]"
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content align="center" side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200 space-y-1',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs">
|
||||
This privilege cannot be updated via the dashboard
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content align="center" side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200 space-y-1',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs">
|
||||
This privilege cannot be updated via the dashboard
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -72,19 +72,21 @@ const ColumnList: FC<Props> = ({
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateColumns && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to create columns
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to create columns
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
@@ -140,19 +142,21 @@ const ColumnList: FC<Props> = ({
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateColumns && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to edit columns
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to edit columns
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
|
||||
@@ -167,19 +171,21 @@ const ColumnList: FC<Props> = ({
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateColumns && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to delete columns
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to delete columns
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
@@ -134,19 +134,21 @@ const TableList: FC<Props> = ({
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateTables && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to create tables
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to create tables
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
@@ -228,19 +230,21 @@ const TableList: FC<Props> = ({
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateTables && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to edit tables
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to edit tables
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
|
||||
@@ -255,19 +259,21 @@ const TableList: FC<Props> = ({
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateTables && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to delete tables
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to delete tables
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
|
||||
|
||||
@@ -82,19 +82,21 @@ const TriggerList: FC<Props> = ({ filterString, schema, editTrigger, deleteTrigg
|
||||
<Tooltip.Trigger>
|
||||
<Button as="span" disabled type="default" icon={<IconMoreVertical />} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="left">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update triggers
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="left">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update triggers
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -80,19 +80,21 @@ const TriggersList: FC<any> = ({
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canCreateTriggers && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to create triggers
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to create triggers
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
@@ -181,19 +181,21 @@ const WrapperRow: FC<Props> = ({ wrappers = [], wrapperMeta, isOpen, onOpen }) =
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
{!canManageWrappers && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to edit wrappers
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to edit wrappers
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
@@ -208,19 +210,21 @@ const WrapperRow: FC<Props> = ({ wrappers = [], wrapperMeta, isOpen, onOpen }) =
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
{!canManageWrappers && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to add wrappers
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to add wrappers
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
@@ -131,19 +131,21 @@ const WrappersDisabledState = () => {
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canToggleWrappers && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to enable Wrappers for this project
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to enable Wrappers for this project
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
@@ -26,19 +26,21 @@ const WrapperDropdown: FC<Props> = ({ buttonText = 'Add wrapper', align = 'end'
|
||||
{buttonText}
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to add wrappers
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to add wrappers
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)
|
||||
}
|
||||
|
||||
+15
-13
@@ -276,19 +276,21 @@ const EdgeFunctionDetails: FC<Props> = () => {
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateEdgeFunction && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to delete an edge function
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to delete an edge function
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</Alert>
|
||||
|
||||
@@ -78,19 +78,21 @@ const EdgeFunctionsListItem: FC<Props> = ({ function: item }) => {
|
||||
<p className="text-sm text-scale-1000">{dayjs(item.updated_at).fromNow()}</p>
|
||||
</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
Last updated on {dayjs(item.updated_at).format('DD MMM, YYYY HH:mm')}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
Last updated on {dayjs(item.updated_at).format('DD MMM, YYYY HH:mm')}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</Table.td>
|
||||
<Table.td className="lg:table-cell">
|
||||
|
||||
@@ -143,7 +143,7 @@ const TerminalInstructions: FC<Props> = ({ closable = false, removeBorder = fals
|
||||
href="https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions"
|
||||
>
|
||||
<a target="_blank" rel="noreferrer">
|
||||
<Button as="a" type="default" iconRight={<IconCode />}>
|
||||
<Button type="default" iconRight={<IconCode />}>
|
||||
Examples
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
@@ -187,19 +187,21 @@ const PaymentMethods: FC<Props> = ({
|
||||
<Tooltip.Trigger>
|
||||
<Button disabled as="span" type="outline" icon={<IconX />} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'w-48 border border-scale-200 text-center', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
Your default payment method cannot be deleted
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'w-48 border border-scale-200 text-center', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
Your default payment method cannot be deleted
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
) : (
|
||||
<Dropdown
|
||||
|
||||
@@ -101,19 +101,21 @@ const InviteMemberButton = ({
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canInviteMembers && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to invite a member to this organization
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to invite a member to this organization
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
<Modal
|
||||
|
||||
@@ -133,19 +133,21 @@ const MemberActions: FC<Props> = ({ member, roles }) => {
|
||||
<Tooltip.Trigger>
|
||||
<Button as="span" type="text" icon={<IconMoreHorizontal />} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to manage this team member
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to manage this team member
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -214,33 +214,38 @@ const MembersView = ({ searchString }: MembersViewProps) => {
|
||||
</Listbox>
|
||||
</Tooltip.Trigger>
|
||||
{memberIsPendingInvite ? (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
Role can only be changed after the user has accepted the invite
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
Role can only be changed after the user has accepted the
|
||||
invite
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
) : !memberIsUser && !canRemoveRole ? (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to manage this team member
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to manage this team member
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
@@ -252,20 +257,22 @@ const MembersView = ({ searchString }: MembersViewProps) => {
|
||||
<Tooltip.Trigger>
|
||||
<IconAlertCircle size={16} strokeWidth={1.5} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
This user has an invalid role, please reach out to us via
|
||||
support
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
This user has an invalid role, please reach out to us via
|
||||
support
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+13
-11
@@ -17,17 +17,19 @@ const RolesHelperModal: FC<Props> = ({}) => {
|
||||
<Tooltip.Trigger>
|
||||
<IconInfo size={14} strokeWidth={2} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="top">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Only available in Team/Enterprise plan.</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="top">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Only available in Team/Enterprise plan.</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)
|
||||
|
||||
|
||||
@@ -97,19 +97,21 @@ const TeamSettings = () => {
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canLeave && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
An organization requires at least 1 owner
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
An organization requires at least 1 owner
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
@@ -99,7 +99,7 @@ const ReportFilterBar: FC<Props> = ({
|
||||
]
|
||||
const [showAdder, setShowAdder] = useState(false)
|
||||
const [currentProductFilter, setCurrentProductFilter] = useState<
|
||||
null | typeof PRODUCT_FILTERS[number]
|
||||
null | (typeof PRODUCT_FILTERS)[number]
|
||||
>(null)
|
||||
const [addFilterValues, setAddFilterValues] = useState<ReportFilterItem>({
|
||||
key: filterKeys[0],
|
||||
@@ -116,7 +116,7 @@ const ReportFilterBar: FC<Props> = ({
|
||||
}
|
||||
|
||||
const handleProductFilterChange = async (
|
||||
nextProductFilter: null | typeof PRODUCT_FILTERS[number]
|
||||
nextProductFilter: null | (typeof PRODUCT_FILTERS)[number]
|
||||
) => {
|
||||
const toRemove = PRODUCT_FILTERS.map(
|
||||
(productFilter) =>
|
||||
@@ -156,6 +156,7 @@ const ReportFilterBar: FC<Props> = ({
|
||||
All Requests
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Separator />
|
||||
|
||||
{PRODUCT_FILTERS.map((productFilter) => {
|
||||
const Icon = productFilter.icon
|
||||
return (
|
||||
@@ -163,7 +164,6 @@ const ReportFilterBar: FC<Props> = ({
|
||||
key={productFilter.key}
|
||||
disabled={productFilter.key === currentProductFilter?.key}
|
||||
onClick={() => handleProductFilterChange(productFilter)}
|
||||
className="hover:bg-scale-600"
|
||||
icon={<Icon size={20} className="mr-2" />}
|
||||
>
|
||||
<p
|
||||
|
||||
@@ -46,17 +46,19 @@ const ReportWidget: React.FC<ReportWidgetProps> = (props) => {
|
||||
<Tooltip.Trigger>
|
||||
<IconHelpCircle className="text-scale-1100" size="tiny" strokeWidth={1.5} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 max-w-xs leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">{props.tooltip}</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 max-w-xs leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">{props.tooltip}</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
@@ -80,17 +82,19 @@ const ReportWidget: React.FC<ReportWidgetProps> = (props) => {
|
||||
}}
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 max-w-xs leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Open in Logs Explorer</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 max-w-xs leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Open in Logs Explorer</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ const Reports = () => {
|
||||
return (
|
||||
<Dropdown.Checkbox
|
||||
key={metric.key}
|
||||
checked={config.layout?.find((x: any) => x.attribute === metric.key)}
|
||||
checked={config.layout?.some((x: any) => x.attribute === metric.key)}
|
||||
onChange={(e) => handleChartSelection({ metric, value: e })}
|
||||
>
|
||||
<div className="flex flex-col space-y-0">
|
||||
@@ -360,19 +360,21 @@ const Reports = () => {
|
||||
Add / Remove charts
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update this project's report
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update this project's report
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -112,20 +112,22 @@ const FavouriteButton = () => {
|
||||
return (
|
||||
<>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
{isFavorite ? 'Remove' : 'Add'} this query {isFavorite ? 'from' : 'to'} your
|
||||
Favourites
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
{isFavorite ? 'Remove' : 'Add'} this query {isFavorite ? 'from' : 'to'} your
|
||||
Favourites
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
<Tooltip.Trigger type="button">
|
||||
<Button
|
||||
type="text"
|
||||
|
||||
@@ -64,17 +64,19 @@ const SavingIndicator: FC<Props> = ({ updateSqlSnippet }) => {
|
||||
<Tooltip.Trigger>
|
||||
<IconLoader className="animate-spin" size={14} strokeWidth={2} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Saving changes...</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Saving changes...</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
{(content.savingState === 'CREATING_FAILED' || content.savingState === 'UPDATING_FAILED') && (
|
||||
@@ -85,17 +87,19 @@ const SavingIndicator: FC<Props> = ({ updateSqlSnippet }) => {
|
||||
<Tooltip.Trigger>
|
||||
<IconCheck className="text-brand-900" size={14} strokeWidth={3} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200 ',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">All changes saved</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200 ',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">All changes saved</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
<span className="text-sm text-scale-1000">
|
||||
|
||||
Loaded 100 of 151 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user