Merge pull request #12187 from supabase/feat/add-autodocs-to-table-editor

feat/add-autodocs-to-table-editor
This commit is contained in:
Terry Sutton authored and GitHub committed 2023-02-23 12:37:17 -03:30
commit 444c29365a
33 files changed
+812 -315

No files matched your search

+1
View File
@@ -305,6 +305,7 @@ export * from './src/components/Icon/icons/IconSun'
export * from './src/components/Icon/icons/IconSunrise'
export * from './src/components/Icon/icons/IconSunset'
export * from './src/components/Icon/icons/IconTablet'
export * from './src/components/Icon/icons/IconTable'
export * from './src/components/Icon/icons/IconTag'
export * from './src/components/Icon/icons/IconTarget'
export * from './src/components/Icon/icons/IconTerminal'
+3 -2
View File
@@ -234,6 +234,7 @@ export default {
IconSunrise: 'src/components/Icon/icons/IconSunrise/index.tsx',
IconSunset: 'src/components/Icon/icons/IconSunset/index.tsx',
IconTablet: 'src/components/Icon/icons/IconTablet/index.tsx',
IconTable: 'src/components/Icon/icons/IconTable/index.tsx',
IconTag: 'src/components/Icon/icons/IconTag/index.tsx',
IconTarget: 'src/components/Icon/icons/IconTarget/index.tsx',
IconTerminal: 'src/components/Icon/icons/IconTerminal/index.tsx',
@@ -284,5 +285,5 @@ export default {
IconZapOff: 'src/components/Icon/icons/IconZapOff/index.tsx',
IconZap: 'src/components/Icon/icons/IconZap/index.tsx',
IconZoomIn: 'src/components/Icon/icons/IconZoomIn/index.tsx',
IconZoomOut: 'src/components/Icon/icons/IconZoomOut/index.tsx'
}
IconZoomOut: 'src/components/Icon/icons/IconZoomOut/index.tsx',
}
+4
View File
@@ -939,6 +939,10 @@
"name": "Tablet",
"path": "react-feather/dist/icons/tablet"
},
"Table": {
"name": "Table",
"path": "react-feather/dist/icons/table"
},
"Tag": {
"name": "Tag",
"path": "react-feather/dist/icons/tag"
@@ -241,6 +241,7 @@ export const Sun: Icon = require(`react-feather/dist/icons/sun`)
export const Sunrise: Icon = require(`react-feather/dist/icons/sunrise`)
export const Sunset: Icon = require(`react-feather/dist/icons/sunset`)
export const Tablet: Icon = require(`react-feather/dist/icons/tablet`)
export const Table: Icon = require(`react-feather/dist/icons/table`)
export const Tag: Icon = require(`react-feather/dist/icons/tag`)
export const Target: Icon = require(`react-feather/dist/icons/target`)
export const Terminal: Icon = require(`react-feather/dist/icons/terminal`)
@@ -0,0 +1,13 @@
// DO NOT EDIT
// this file is generated by /internals/populate-icons script
import * as React from 'react'
// @ts-ignore
import { Table } from 'react-feather'
import IconBase from './../../IconBase'
function IconTable(props: any) {
return <IconBase icon={Table} {...props} />
}
export default IconTable
@@ -0,0 +1 @@
export { default as IconTable } from './IconTable'
@@ -234,6 +234,7 @@ export * as IconSun from './IconSun'
export * as IconSunrise from './IconSunrise'
export * as IconSunset from './IconSunset'
export * as IconTablet from './IconTablet'
export * as IconTable from './IconTable'
export * as IconTag from './IconTag'
export * as IconTarget from './IconTarget'
export * as IconTerminal from './IconTerminal'
@@ -22,7 +22,7 @@ interface CustomProps {
children?: React.ReactNode
header?: string | React.ReactNode
visible: boolean
size?: 'medium' | 'large' | 'xlarge'
size?: 'medium' | 'large' | 'xlarge' | 'xxlarge'
loading?: boolean
align?: 'right' | 'left'
hideFooter?: boolean
@@ -998,6 +998,7 @@ export default {
medium: `w-screen max-w-md h-full`,
large: `w-screen max-w-2xl h-full`,
xlarge: `w-screen max-w-3xl h-full`,
xxlarge: `w-screen max-w-4xl h-full`,
},
align: {
left: `
+9
View File
@@ -64,6 +64,15 @@ export interface SupabaseGridProps {
*/
onEditRow?: (row: SupaRow) => void
onError?: (error: any) => void
/**
* Toggle api preview panel open
*/
apiPreviewPanelOpen?: boolean
setApiPreviewPanelOpen?: () => void
/**
* Refresh the docs after a change is made to the table
*/
refreshDocs: () => void
onExpandJSONEditor: (column: string, row: SupaRow) => void
}
@@ -0,0 +1,4 @@
export type showApiKey = {
key: string
name: string
}
@@ -0,0 +1,121 @@
import { FC, useState } from 'react'
import Link from 'next/link'
import React from 'react'
import { Button, IconDownload, IconExternalLink } from 'ui'
import { API_ADMIN_URL } from 'lib/constants'
import { get } from 'lib/common/fetch'
import { useParams, useStore } from 'hooks'
import CodeSnippet from 'components/to-be-cleaned/Docs/CodeSnippet'
interface Props {
selectedLang: string
}
export default function GeneratingTypes({ selectedLang }: Props) {
const { ref } = useParams()
const { ui } = useStore()
const [isGeneratingTypes, setIsGeneratingTypes] = useState(false)
const generateTypes = async () => {
setIsGeneratingTypes(true)
const res = await get(`${API_ADMIN_URL}/projects/${ref}/types/typescript`)
if (!res.error) {
let element = document.createElement('a')
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(res.types))
element.setAttribute('download', 'supabase.ts')
element.style.display = 'none'
document.body.appendChild(element)
element.click()
document.body.removeChild(element)
ui.setNotification({
category: 'success',
message: `Successfully generated types! File is being downloaded`,
})
} else {
ui.setNotification({
category: 'error',
message: `Failed to generate types: ${res.error.message}`,
error: res.error,
})
}
setIsGeneratingTypes(false)
}
return (
<>
<h2 className="doc-heading flex items-center justify-between">
<span>Generating types</span>
<Link href="https://supabase.com/docs/guides/database/api/generating-types">
<a target="_blank">
<Button type="default" icon={<IconExternalLink />}>
Documentation
</Button>
</a>
</Link>
</h2>
<div className="doc-section">
<article className="text ">
<p>
Supabase APIs are generated from your database, which means that we can use database
introspection to generate type-safe API definitions.
</p>
<p>
You can generate types from your database either through the{' '}
<Link href="https://supabase.com/docs/guides/database/api/generating-types">
Supabase CLI
</Link>
, or by downloading the types file via the button on the right and importing it in your
application within <code>src/index.ts</code>.
</p>
</article>
<article
className={`code ${selectedLang === 'js' ? 'flex items-center justify-center' : ''}`}
>
<div className="grid gap-2">
<p className="text-center">
{selectedLang === 'js' && (
<Button
type="default"
disabled={isGeneratingTypes}
loading={isGeneratingTypes}
icon={<IconDownload strokeWidth={1.5} />}
onClick={generateTypes}
>
Generate and download types
</Button>
)}
</p>
<p className="text-xs text-scale-1100 bg-scale-200 p-4 mt-2">
Remember to re-generate and download this file as you make changes to your tables.
</p>
</div>
<CodeSnippet selectedLang={selectedLang} snippet={localSnippets.cliLogin()} />
<CodeSnippet
selectedLang={selectedLang}
snippet={localSnippets.generateTypes(ref ?? '')}
/>
</article>
</div>
</>
)
}
const localSnippets = {
cliLogin: () => ({
title: 'Login via the CLI with your Personal Access Token',
bash: {
code: `
npx supabase login
`,
},
}),
generateTypes: (ref: string) => ({
title: 'Generate types',
bash: {
code: `
npx supabase gen types typescript --project-id "${ref}" --schema public > types/supabase.ts
`,
},
}),
}
@@ -0,0 +1,110 @@
import { FC } from 'react'
import { Button, Dropdown, IconKey } from 'ui'
import { checkPermissions } from 'hooks'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { showApiKey } from 'components/interfaces/Docs/Docs.types'
const DEFAULT_KEY = { name: 'hide', key: 'SUPABASE_KEY' }
interface Props {
selectedLang: string
setSelectedLang: (selectedLang: string) => void
showApiKey: showApiKey
setShowApiKey: (showApiKey: showApiKey) => void
apiKey: string | undefined
autoApiService: any
}
const LangSelector: FC<Props> = ({
selectedLang,
setSelectedLang,
showApiKey,
setShowApiKey,
apiKey,
autoApiService,
}) => {
const canReadServiceKey = checkPermissions(
PermissionAction.READ,
'service_api_keys.service_role_key'
)
return (
<div className="p-1 w-1/2 ml-auto">
<div className="z-0 flex justify-end">
<button
type="button"
onClick={() => setSelectedLang('js')}
className={`${
selectedLang == 'js'
? 'bg-scale-300 font-medium text-scale-1200 dark:bg-scale-200'
: 'bg-scale-100 text-scale-900 dark:bg-scale-100'
} relative inline-flex items-center border-r border-scale-200 p-1 px-2 text-sm transition hover:text-scale-1200 focus:outline-none`}
>
JavaScript
</button>
<button
type="button"
onClick={() => setSelectedLang('bash')}
className={`${
selectedLang == 'bash'
? 'bg-scale-300 font-medium text-scale-1200 dark:bg-scale-200'
: 'bg-scale-100 text-scale-900 dark:bg-scale-100'
} relative inline-flex items-center border-r border-scale-200 p-1 px-2 text-sm transition hover:text-scale-1200 focus:outline-none`}
>
Bash
</button>
{selectedLang == 'bash' && (
<div className="flex">
<div className="flex items-center gap-2 p-1 pl-2 text-xs text-scale-900">
<IconKey size={12} strokeWidth={1.5} />
<span>Project API key :</span>
</div>
<Dropdown
align="end"
side="bottom"
className="cursor-pointer border-none bg-transparent p-0 pl-2 pr-8 text-sm text-scale-900"
overlay={
<>
<Dropdown.Item key="hide" onClick={() => setShowApiKey(DEFAULT_KEY)}>
hide
</Dropdown.Item>
{apiKey && (
<Dropdown.Item
key="anon"
onClick={() =>
setShowApiKey({
key: apiKey,
name: 'anon (public)',
})
}
>
anon (public)
</Dropdown.Item>
)}
{canReadServiceKey && (
<Dropdown.Item
key="service"
onClick={() =>
setShowApiKey({
key: autoApiService.serviceApiKey,
name: 'service_role (secret)',
})
}
>
service_role (secret)
</Dropdown.Item>
)}
</>
}
>
<Button type="default">{showApiKey.name}</Button>
</Dropdown>
</div>
)}
</div>
</div>
)
}
export default LangSelector
@@ -2,6 +2,7 @@ import Snippets from 'components/to-be-cleaned/Docs/Snippets'
import CodeSnippet from 'components/to-be-cleaned/Docs/CodeSnippet'
import Param from 'components/to-be-cleaned/Docs/Param'
import Description from 'components/to-be-cleaned/Docs/Description'
import { IconTable } from 'ui'
const ResourceContent = ({
autoApiService,
@@ -29,12 +30,18 @@ const ResourceContent = ({
return (
<>
<h2 className="text-scale-1200mt-0">
<span className="px-6 py-2 text-2xl">{resourceId}</span>
<h2 className="doc-section__table-name text-scale-1200 mt-0 flex items-center px-6 gap-2">
<span className="bg-slate-300 dark:bg-slate-400 p-2 rounded-lg">
<IconTable size="small" />
</span>
<span className="text-2xl font-bold">{resourceId}</span>
</h2>
<div className="doc-section">
<article className="text ">
<article className="text">
<label className="font-mono text-xs uppercase text-scale-900 inline-block mb-2">
Description
</label>
<Description
content={description}
metadata={{ table: resourceId }}
@@ -47,7 +54,7 @@ const ResourceContent = ({
<div>
{properties.map((x) => (
<div className="doc-section py-4" key={x.id}>
<article className="text">
<div className="text">
<Param
key={x.id}
name={x.id}
@@ -61,8 +68,8 @@ const ResourceContent = ({
}}
onDesciptionUpdated={refreshDocs}
/>
</article>
<article className="code">
</div>
<div className="code">
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.readColumns({
@@ -73,7 +80,7 @@ const ResourceContent = ({
columnName: x.id,
})}
/>
</article>
</div>
</div>
))}
</div>
@@ -87,8 +94,8 @@ const ResourceContent = ({
To read rows in <code>{resourceId}</code>, use the <code>select</code> method.
</p>
<p>
<a href="https://supabase.com/docs/client/select" target="_blank">
Learn more.
<a href="https://supabase.com/docs/reference/javascript/select" target="_blank">
Learn more
</a>
</p>
</article>
@@ -120,8 +127,11 @@ const ResourceContent = ({
<h4 className="mt-0 text-white">Filtering</h4>
<p>Supabase provides a wide range of filters.</p>
<p>
<a href="https://supabase.com/docs/client/using-filters" target="_blank">
Learn more.
<a
href="https://supabase.com/docs/reference/javascript/using-filters"
target="_blank"
>
Learn more
</a>
</p>
</article>
@@ -147,8 +157,8 @@ const ResourceContent = ({
<code>insert</code> will also return the replaced values for UPSERT.
</p>
<p>
<a href="https://supabase.com/docs/client/insert" target="_blank">
Learn more.
<a href="https://supabase.com/docs/reference/javascript/insert" target="_blank">
Learn more
</a>
</p>
</article>
@@ -183,8 +193,8 @@ const ResourceContent = ({
<code>update</code> will also return the replaced values for UPDATE.
</p>
<p>
<a href="https://supabase.com/docs/client/update" target="_blank">
Learn more.
<a href="https://supabase.com/docs/reference/javascript/update" target="_blank">
Learn more
</a>
</p>
</article>
@@ -207,8 +217,8 @@ const ResourceContent = ({
default, so remember to specify your filters!
</p>
<p>
<a href="https://supabase.com/docs/client/delete" target="_blank">
Learn more.
<a href="https://supabase.com/docs/reference/javascript/delete" target="_blank">
Learn more
</a>
</p>
</article>
@@ -230,8 +240,8 @@ const ResourceContent = ({
users depending on Row Level Security (RLS) policies.
</p>
<p>
<a href="https://supabase.com/docs/client/subscribe" target="_blank">
Learn more.
<a href="https://supabase.com/docs/reference/javascript/subscribe" target="_blank">
Learn more
</a>
</p>
</article>
@@ -264,21 +274,6 @@ const ResourceContent = ({
</article>
</div>
</>
<>
<h3 className="text-scale-1200 mt-4 px-6">Much more</h3>
<div className="doc-section py-4">
<article className="text ">
<p>
These docs are a work in progress! See our{' '}
<a href="https://supabase.com/docs/" target="_blank">
docs
</a>{' '}
for the additional functionality Supabase has to offer.
</p>
</article>
<article className="code"></article>
</div>
</>
</>
)
}
@@ -1,7 +1,7 @@
import { FC } from 'react'
import Link from 'next/link'
import { Button, IconAlertCircle, IconCode, IconLock } from 'ui'
import * as Tooltip from '@radix-ui/react-tooltip'
import { Button, IconAlertCircle, IconLock } from 'ui'
import type { PostgresPolicy, PostgresTable } from '@supabase/postgres-meta'
import { useStore, checkPermissions } from 'hooks'
@@ -9,9 +9,17 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
interface Props {
table: PostgresTable
apiPreviewPanelOpen: boolean
setApiPreviewPanelOpen: (apiPreviewPanelOpen: boolean) => void
refreshDocs: () => void
}
const GridHeaderActions: FC<Props> = ({ table }) => {
const GridHeaderActions: FC<Props> = ({
table,
apiPreviewPanelOpen,
setApiPreviewPanelOpen,
refreshDocs,
}) => {
const { ui, meta } = useStore()
const projectRef = ui.selectedProject?.ref
const policies = meta.policies.list((policy: PostgresPolicy) => policy.table_id === table.id)
@@ -20,6 +28,24 @@ const GridHeaderActions: FC<Props> = ({ table }) => {
!checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') &&
!checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns')
function handlePreviewToggle() {
setApiPreviewPanelOpen(!apiPreviewPanelOpen)
refreshDocs()
}
const RenderAPIPreviewToggle = () => {
return (
<Button
size="tiny"
type="default"
icon={<IconCode size={14} strokeWidth={2} />}
onClick={handlePreviewToggle}
>
API Quickstart
</Button>
)
}
return (
<div className="flex items-center space-x-3">
{isReadOnly && (
@@ -44,6 +70,9 @@ const GridHeaderActions: FC<Props> = ({ table }) => {
</Tooltip.Content>
</Tooltip.Root>
)}
<div className="mt-[1px]">
<RenderAPIPreviewToggle />
</div>
<Link href={`/project/${projectRef}/auth/policies#${table.id}`}>
<a>
<Button
@@ -6,12 +6,20 @@ import type { PostgresColumn, PostgresTable } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { SchemaView } from 'types'
import { checkPermissions, useFlag, useStore } from 'hooks'
import { checkPermissions, useFlag, useStore, useParams } from 'hooks'
import GridHeaderActions from './GridHeaderActions'
import NotFoundState from './NotFoundState'
import SidePanelEditor from './SidePanelEditor'
import { Dictionary, parseSupaTable, SupabaseGrid, SupabaseGridRef } from 'components/grid'
import { IconBookOpen, SidePanel } from 'ui'
import ActionBar from './SidePanelEditor/ActionBar'
import { GeneralContent, ResourceContent } from '../Docs'
import { useProjectApiQuery } from 'data/config/project-api-query'
import { useProjectJsonSchemaQuery } from 'data/docs/project-json-schema-query'
import { snakeToCamel } from 'lib/helpers'
import { JsonEditValue } from './SidePanelEditor/RowEditor/RowEditor.types'
import LangSelector from '../Docs/LangSelector'
import GeneratingTypes from '../Docs/GeneratingTypes'
interface Props {
/** Theme for the editor */
@@ -61,11 +69,27 @@ const TableGridEditor: FC<Props> = ({
}) => {
const { meta, ui, vault } = useStore()
const router = useRouter()
const { ref: projectRef, page, id } = useParams()
const gridRef = useRef<SupabaseGridRef>(null)
const projectRef = ui.selectedProject?.ref
const tables = meta.tables.list()
const { data: settings } = useProjectApiQuery({ projectRef: projectRef })
const autoApiService = {
...settings?.autoApiService,
endpoint: `${settings?.autoApiService.protocol ?? 'https'}://${
settings?.autoApiService.endpoint ?? '-'
}`,
}
const DEFAULT_KEY = { name: 'hide', key: 'SUPABASE_KEY' }
const isVaultEnabled = useFlag('vaultExtension')
const [encryptedColumns, setEncryptedColumns] = useState([])
const [apiPreviewPanelOpen, setApiPreviewPanelOpen] = useState(false)
const [selectedLang, setSelectedLang] = useState<any>('js')
const [showApiKey, setShowApiKey] = useState<any>(DEFAULT_KEY)
const isReadOnly =
!checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') &&
@@ -76,6 +100,48 @@ const TableGridEditor: FC<Props> = ({
setEncryptedColumns(columns)
}
function getResourcesFromJsonSchema(value: any) {
const { paths } = value || {}
const functionPath = 'rpc/'
let resources: any = {}
Object.entries(paths || []).forEach(([name, val]) => {
let trimmed = name.slice(1)
let id = trimmed.replace(functionPath, '')
let displayName = id.replace(/_/g, ' ')
let camelCase = snakeToCamel(id)
let enriched = { id, displayName, camelCase }
if (!trimmed.length) return
else resources[id] = enriched
})
return resources
}
const apiService = settings?.autoApiService
const anonKey = apiService?.service_api_keys.find((x) => x.name === 'anon key')
? apiService.defaultApiKey
: undefined
const swaggerUrl = settings?.autoApiService?.restUrl
const {
data: jsonSchema,
error: jsonSchemaError,
refetch,
} = useProjectJsonSchemaQuery({
projectRef,
swaggerUrl,
apiKey: anonKey,
})
if (jsonSchemaError) console.log('jsonSchemaError', jsonSchemaError)
const resources = getResourcesFromJsonSchema(jsonSchema)
const refreshDocs = async () => {
await refetch()
}
useEffect(() => {
if (selectedTable !== undefined && selectedTable.id !== undefined && isVaultEnabled) {
getEncryptedColumns(selectedTable)
@@ -83,7 +149,7 @@ const TableGridEditor: FC<Props> = ({
}, [selectedTable?.id])
if (isUndefined(selectedTable)) {
return <NotFoundState id={Number(router.query.id)} />
return <NotFoundState id={Number(id)} />
}
const tableId = selectedTable?.id
@@ -177,8 +243,16 @@ const TableGridEditor: FC<Props> = ({
editable={!isReadOnly && canUpdateTables && canEditViaTableEditor}
schema={selectedTable.schema}
table={gridTable}
refreshDocs={refreshDocs}
headerActions={
canEditViaTableEditor && <GridHeaderActions table={selectedTable as PostgresTable} />
canEditViaTableEditor && (
<GridHeaderActions
table={selectedTable as PostgresTable}
apiPreviewPanelOpen={apiPreviewPanelOpen}
setApiPreviewPanelOpen={setApiPreviewPanelOpen}
refreshDocs={refreshDocs}
/>
)
}
onAddColumn={onAddColumn}
onEditColumn={onSelectEditColumn}
@@ -206,6 +280,82 @@ const TableGridEditor: FC<Props> = ({
closePanel={onClosePanel}
/>
)}
<SidePanel
key="WrapperTableEditor"
size="xxlarge"
visible={apiPreviewPanelOpen}
onCancel={() => setApiPreviewPanelOpen(false)}
header={
<span className="flex items-center gap-2">
<IconBookOpen size="tiny" />
API
</span>
}
customFooter={
<ActionBar
backButtonLabel="Close"
hideApply={true}
formId="wrapper-table-editor-form"
closePanel={() => setApiPreviewPanelOpen(false)}
/>
}
>
<div className="Docs Docs--table-editor">
<SidePanel.Content>
{jsonSchemaError ? (
<div className="p-6 mx-auto text-center sm:w-full md:w-3/4">
<div className="text-scale-1000">
<p>Error connecting to API</p>
<p>{`${jsonSchemaError}`}</p>
</div>
</div>
) : (
<>
{jsonSchema ? (
<>
<div className="sticky top-0 z-10 bg-scale-100 dark:bg-scale-300">
<LangSelector
selectedLang={selectedLang}
setSelectedLang={setSelectedLang}
showApiKey={showApiKey}
setShowApiKey={setShowApiKey}
apiKey={anonKey}
autoApiService={autoApiService}
/>
</div>
<GeneralContent
autoApiService={autoApiService}
selectedLang={selectedLang}
showApiKey={true}
page={page}
/>
<GeneratingTypes selectedLang={selectedLang} />
{jsonSchema?.definitions && (
<ResourceContent
autoApiService={autoApiService}
selectedLang={selectedLang}
resourceId={tables.find((table) => table.id === Number(id))?.name}
resources={resources}
definitions={jsonSchema.definitions}
paths={jsonSchema.paths}
showApiKey={showApiKey.key}
refreshDocs={refreshDocs}
/>
)}
</>
) : (
<div className="p-6 mx-auto text-center sm:w-full md:w-3/4">
<h3 className="text-lg">Building docs ...</h3>
</div>
)}
</>
)}
</SidePanel.Content>
</div>
</SidePanel>
</>
)
}
@@ -3,12 +3,12 @@ import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock'
const CodeSnippet = ({ selectedLang, snippet }) => {
if (!snippet[selectedLang]) return null
return (
<>
<div className="codeblock-container">
<h4>{snippet.title}</h4>
<SimpleCodeBlock className={snippet[selectedLang].language}>
{snippet[selectedLang].code}
</SimpleCodeBlock>
</>
</div>
)
}
export default CodeSnippet
@@ -1,59 +0,0 @@
import Snippets from '../Snippets'
import CodeSnippet from '../CodeSnippet'
export default function Introduction({ autoApiService, selectedLang }) {
return (
<>
<h2 className="doc-heading">Introduction</h2>
<div className="doc-section">
<article className="text ">
<p>
This API provides an easy way to integrate with your Postgres database. The API
documentation below is specifically generated for your database.
</p>
<p>
This is an <b>auto-generating</b> API, so as you make changes to your database, this
documentation will change too.
</p>
<p>
<b>Please note:</b> if you make changes to a field (column) name or type, the API
interface for those fields will change correspondingly. Therefore, please make sure to
update your API implementation accordingly whenever you make changes to your Supabase
schema from the graphical interface.
</p>
</article>
</div>
<h2 className="doc-heading">API URL</h2>
<div className="doc-section ">
<article className="text ">
<p>The API URL for your project.</p>
</article>
<article className="code">
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.endpoint(autoApiService.endpoint)}
/>
</article>
</div>
<h2 className="doc-heading">Client Libraries</h2>
<div className="doc-section">
<article className="text ">
<p>Your API consists of both a RESTful interface and a Realtime interface.</p>
<p>
For interacting with the Realtime streams, we provide client libraries that handle the
websockets.
</p>
</article>
<article className="code">
<CodeSnippet selectedLang={selectedLang} snippet={Snippets.install()} />
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.init(autoApiService.endpoint)}
/>
</article>
</div>
</>
)
}
@@ -0,0 +1,112 @@
import Snippets from '../Snippets'
import CodeSnippet from '../CodeSnippet'
import Image from 'next/image'
import { useStore } from 'hooks'
import { AutoApiService } from 'data/config/project-api-query'
const libs = [
{
name: 'Javascript',
url: 'https://supabase.com/docs/reference/javascript/introduction',
icon: 'javascript',
},
{ name: 'Flutter', url: 'https://supabase.com/docs/reference/dart/introduction', icon: 'dart' },
{
name: 'Python',
url: 'https://supabase.com/docs/reference/python/introduction',
icon: 'python',
},
{ name: 'C#', url: 'https://supabase.com/docs/reference/csharp/introduction', icon: 'csharp' },
]
interface Props {
autoApiService: AutoApiService
selectedLang: string
}
export default function Introduction({ autoApiService, selectedLang }: Props) {
const { ui } = useStore()
const { isDarkTheme } = ui
return (
<>
<h2 className="doc-heading">Introduction</h2>
<div className="doc-section doc-section--introduction">
<article className="text ">
<p>
This API provides an easy way to integrate with your Postgres database. The API
documentation below is specifically generated for your database.
</p>
<p>
This is an <b>auto-generating</b> API, so as you make changes to your database, this
documentation will change too.
</p>
<p>
<b>Note:</b> if you make changes to a field (column) name or type, the API interface for
those fields will change correspondingly. Therefore, please make sure to update your API
implementation accordingly whenever you make changes to your Supabase schema from the
graphical interface.
</p>
<div className="not-prose mb-6">
<p>Read the reference documentation:</p>
<div className="flex items-center gap-4 mt-2">
{libs.map((lib) => (
<a
href={lib.url}
target="_blank"
rel="noreferrer"
className="
flex items-center gap-1 rounded-md px-3 py-1
bg-scale-300 dark:bg-scale-500
hover:bg-scale-500 hover:dark:bg-scale-700 transition-colors
!text-scale-1100 hover:text-scale-1100
"
>
<Image
src={`/img/icons/reference-${isDarkTheme ? lib.icon : `${lib.icon}-light`}.svg`}
width={16}
height={16}
alt={lib.name}
/>
{lib.name}
</a>
))}
</div>
</div>
</article>
</div>
<h2 className="doc-heading">API URL</h2>
<div className="doc-section ">
<article className="text ">
<p>The API URL for your project.</p>
</article>
<article className="code">
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.endpoint(autoApiService.endpoint)}
/>
</article>
</div>
<h2 className="doc-heading">Client Libraries</h2>
<div className="doc-section doc-section--client-libraries">
<article className="text">
<p>Your API consists of both a RESTful interface and a Realtime interface.</p>
<p>
For interacting with the Realtime streams, we provide client libraries that handle the
websockets.
</p>
</article>
<article className="code">
<CodeSnippet selectedLang={selectedLang} snippet={Snippets.install()} />
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.init(autoApiService.endpoint)}
/>
</article>
</div>
</>
)
}
@@ -1,10 +1,8 @@
import { FC, useState } from 'react'
import { FC } from 'react'
import Link from 'next/link'
import { Button, IconDownload, IconExternalLink } from 'ui'
import { useParams, useStore } from 'hooks'
import { API_ADMIN_URL } from 'lib/constants'
import { get } from 'lib/common/fetch'
import { useParams } from 'hooks'
import CodeSnippet from '../../CodeSnippet'
import GeneratingTypes from 'components/interfaces/Docs/GeneratingTypes'
interface Props {
selectedLang: string
@@ -12,35 +10,6 @@ interface Props {
const Introduction: FC<Props> = ({ selectedLang }) => {
const { ref } = useParams()
const { ui } = useStore()
const [isGeneratingTypes, setIsGeneratingTypes] = useState(false)
const generateTypes = async () => {
setIsGeneratingTypes(true)
const res = await get(`${API_ADMIN_URL}/projects/${ref}/types/typescript`)
if (!res.error) {
let element = document.createElement('a')
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(res.types))
element.setAttribute('download', 'supabase.ts')
element.style.display = 'none'
document.body.appendChild(element)
element.click()
document.body.removeChild(element)
ui.setNotification({
category: 'success',
message: `Successfully generated types! File is being downloaded`,
})
} else {
ui.setNotification({
category: 'error',
message: `Failed to generate types: ${res.error.message}`,
error: res.error,
})
}
setIsGeneratingTypes(false)
}
return (
<>
@@ -65,48 +34,7 @@ const Introduction: FC<Props> = ({ selectedLang }) => {
<article className="code"></article>
</div>
<h2 className="doc-heading flex items-center justify-between">
<span>Generating types</span>
<Link href="https://supabase.com/docs/guides/database/api/generating-types">
<a target="_blank">
<Button type="default" icon={<IconExternalLink />}>
Documentation
</Button>
</a>
</Link>
</h2>
<div className="doc-section">
<article className="text ">
<p>
Supabase APIs are generated from your database, which means that we can use database
introspection to generate type-safe API definitions.
</p>
<p>
You can generate types from your database either through the{' '}
<Link href="https://supabase.com/docs/guides/database/api/generating-types">Supabase CLI</Link>,
or by downloading the types file via the button on the right and importing it in your
application within <code>src/index.ts</code>.
</p>
</article>
<article className={`code ${selectedLang === 'js' ? 'flex items-center' : ''}`}>
{selectedLang === 'js' && (
<Button
type="default"
disabled={isGeneratingTypes}
loading={isGeneratingTypes}
icon={<IconDownload strokeWidth={1.5} />}
onClick={generateTypes}
>
Generate and download types
</Button>
)}
<CodeSnippet selectedLang={selectedLang} snippet={localSnippets.cliLogin()} />
<CodeSnippet
selectedLang={selectedLang}
snippet={localSnippets.generateTypes(ref ?? '')}
/>
</article>
</div>
<GeneratingTypes selectedLang={selectedLang} />
<h2 className="doc-heading">
GraphQL <span className="lowercase">vs</span> Supabase
@@ -141,22 +69,6 @@ const Introduction: FC<Props> = ({ selectedLang }) => {
}
const localSnippets = {
cliLogin: () => ({
title: 'Login via the CLI with your Personal Access Token',
bash: {
code: `
npx supabase login
`,
},
}),
generateTypes: (ref: string) => ({
title: 'Generate types',
bash: {
code: `
npx supabase gen types typescript --project-id "${ref}" --schema public > types/supabase.ts
`,
},
}),
withApollo: () => ({
title: 'With Apollo GraphQL',
bash: {
@@ -1,35 +0,0 @@
import { Badge } from 'ui'
import Description from './Description'
const Param = ({
name,
type,
format,
required,
description,
metadata = {},
onDesciptionUpdated = () => {},
}) => {
return (
<>
<div className="mb-4 flex items-center justify-between ">
<code className="inline-block px-2">{name}</code>
<Badge color={required ? 'amber' : 'scale'}>{required ? 'Required' : 'Optional'}</Badge>
</div>
{format && format != type && (
<div className="mb-4 flex items-center gap-2">
<label className="font-mono text-xs uppercase text-scale-900">Format:</label>
<code className="px-2">{format}</code>
{type}
</div>
)}
{description !== false && (
<>
<label className="font-mono text-xs uppercase text-scale-900">Description</label>
<Description content={description} metadata={metadata} onChange={onDesciptionUpdated} />
</>
)}
</>
)
}
export default Param
@@ -0,0 +1,97 @@
import { FC } from 'react'
import { Badge, IconCode, IconDatabase } from 'ui'
import Description from './Description'
function getColumnType(type: string, format: string) {
// json and jsonb both have type=undefined, so check format instead
if (type === undefined && (format === 'jsonb' || format === 'json')) return 'json'
switch (type) {
case 'string':
return 'string'
case 'integer':
return 'number'
case 'json':
return 'json'
case 'number':
return 'number'
case 'boolean':
return 'boolean'
default:
return ''
}
}
interface Props {
name: string
type: string
format: string
required: boolean
description: boolean
metadata?: any
onDesciptionUpdated?: () => void
}
const Param: FC<Props> = ({
name,
type,
format,
required,
description,
metadata = {},
onDesciptionUpdated = () => {},
}) => {
return (
<>
<div className="mb-4 flex items-center justify-between ">
<div className="flex gap-2 items-center">
<div className="flex items-center gap-2">
<label className="font-mono text-xs uppercase text-scale-900 min-w-[55px]">
Column
</label>
<div className="flex items-center gap-4">
<span className="text-md text-scale-1200 pb-0.5">{name}</span>
</div>
</div>
</div>
<Badge color={required ? 'amber' : 'scale'}>{required ? 'Required' : 'Optional'}</Badge>
</div>
{format && (
<div className="grid gap-2 mt-6">
<div className="mb-4 flex items-center gap-2">
<label className="font-mono text-xs uppercase text-scale-900 min-w-[55px]">Type</label>
<div>
<span className="flex grow-0 bg-slate-300 px-2 py-0.5 rounded-md text-scale-1000">
<span className="flex items-center gap-2 text-sm">
<IconCode size="tiny" />
<span>{getColumnType(type, format)}</span>
</span>
</span>
</div>
</div>
<div className="mb-4 flex items-center gap-2">
<label className="font-mono text-xs uppercase text-scale-900 min-w-[55px]">
Format
</label>
<div>
<span className="flex grow-0 bg-slate-300 px-2 py-0.5 rounded-md text-scale-1000">
<span className="flex items-center gap-2 text-sm">
<IconDatabase size="tiny" />
<span>{format}</span>
</span>
</span>
</div>
</div>
</div>
)}
{description !== false && (
<div className="grid gap-2 mt-2">
<label className="font-mono text-xs uppercase text-scale-900">Description</label>
<Description content={description} metadata={metadata} onChange={onDesciptionUpdated} />
</div>
)}
</>
)
}
export default Param
+13 -82
View File
@@ -1,5 +1,4 @@
import { useRouter } from 'next/router'
import { Button, Dropdown, IconKey } from 'ui'
import { FC, createContext, useContext, useEffect, useState } from 'react'
import { observer, useLocalObservable } from 'mobx-react-lite'
@@ -11,6 +10,7 @@ import { DocsLayout } from 'components/layouts'
import { GeneralContent, ResourceContent, RpcContent } from 'components/interfaces/Docs'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useProjectJsonSchemaQuery } from 'data/docs/project-json-schema-query'
import LangSelector from 'components/interfaces/Docs/LangSelector'
const PageContext = createContext(null)
@@ -79,11 +79,6 @@ const DocView: FC<any> = observer(({}) => {
: undefined
const swaggerUrl = data?.autoApiService?.restUrl
const canReadServiceKey = checkPermissions(
PermissionAction.READ,
'service_api_keys.service_role_key'
)
const {
data: jsonSchema,
error: jsonSchemaError,
@@ -104,7 +99,7 @@ const DocView: FC<any> = observer(({}) => {
if (error || jsonSchemaError)
return (
<div className="mx-auto p-6 text-center sm:w-full md:w-3/4">
<div className="p-6 mx-auto text-center sm:w-full md:w-3/4">
<p className="text-scale-1000">
<p>Error connecting to API</p>
<p>{`${error || jsonSchemaError}`}</p>
@@ -113,7 +108,7 @@ const DocView: FC<any> = observer(({}) => {
)
if (!data || !jsonSchema || !PageState.jsonSchema)
return (
<div className="mx-auto p-6 text-center sm:w-full md:w-3/4">
<div className="p-6 mx-auto text-center sm:w-full md:w-3/4">
<h3 className="text-xl">Building docs ...</h3>
</div>
)
@@ -129,81 +124,17 @@ const DocView: FC<any> = observer(({}) => {
const PAGE_KEY: any = resource || rpc || page || 'index'
return (
<div className="Docs h-full w-full overflow-y-auto" key={PAGE_KEY}>
<div className="w-full h-full overflow-y-auto Docs Docs--api-page" key={PAGE_KEY}>
<div className="Docs--inner-wrapper">
<div className="sticky top-0 z-40 flex w-full flex-row-reverse ">
<div className="bg-scale-100 dark:bg-scale-300" style={{ width: '50%' }}>
<div className="z-0 flex ">
<button
type="button"
onClick={() => setSelectedLang('js')}
className={`${
selectedLang == 'js'
? 'bg-scale-300 font-medium text-scale-1200 dark:bg-scale-200'
: 'bg-scale-100 text-scale-900 dark:bg-scale-100'
} relative inline-flex items-center border-r border-scale-200 p-1 px-2 text-sm transition hover:text-scale-1200 focus:outline-none`}
>
JavaScript
</button>
<button
type="button"
onClick={() => setSelectedLang('bash')}
className={`${
selectedLang == 'bash'
? 'bg-scale-300 font-medium text-scale-1200 dark:bg-scale-200'
: 'bg-scale-100 text-scale-900 dark:bg-scale-100'
} relative inline-flex items-center border-r border-scale-200 p-1 px-2 text-sm transition hover:text-scale-1200 focus:outline-none`}
>
Bash
</button>
{selectedLang == 'bash' && (
<div className="flex">
<div className="flex items-center gap-2 p-1 pl-2 text-xs text-scale-900">
<IconKey size={12} strokeWidth={1.5} />
<span>Project API key :</span>
</div>
<Dropdown
align="end"
side="bottom"
className="cursor-pointer border-none bg-transparent p-0 pl-2 pr-8 text-sm text-scale-900"
overlay={
<>
<Dropdown.Item key="hide" onClick={() => setShowApiKey(DEFAULT_KEY)}>
hide
</Dropdown.Item>
<Dropdown.Item
key="anon"
onClick={() =>
setShowApiKey({
key: anonKey,
name: 'anon (public)',
})
}
>
anon (public)
</Dropdown.Item>
{canReadServiceKey && (
<Dropdown.Item
key="service"
onClick={() =>
setShowApiKey({
key: autoApiService.serviceApiKey,
name: 'service_role (secret)',
})
}
>
service_role (secret)
</Dropdown.Item>
)}
</>
}
>
<Button type="default">{showApiKey.name}</Button>
</Dropdown>
</div>
)}
</div>
</div>
<div className="sticky top-0 z-40 flex flex-row-reverse w-full ">
<LangSelector
selectedLang={selectedLang}
setSelectedLang={setSelectedLang}
showApiKey={showApiKey}
setShowApiKey={setShowApiKey}
apiKey={anonKey}
autoApiService={autoApiService}
/>
</div>
<div className="">
{resource ? (
@@ -0,0 +1 @@
<svg width="10" height="15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M.494 7.974h5v2h-5v-2Z" fill="url(#a)"/><path d="M.494 9.974h5v2h-5v-2Z" fill="url(#b)"/><path fill-rule="evenodd" clip-rule="evenodd" d="M2.682 3a2.318 2.318 0 1 1 4.636 0v1.682h2.155L9.79 5v9l-.318.318h-9L.154 14V5l.319-.318h2.209V3Zm.636 1.682h3.364V3a1.682 1.682 0 0 0-3.364 0v1.682Zm-2.527.636v2.338h4.703l.319.318v4l-.319.319H.791v1.389h8.363V5.318H.791Zm.022 6.338h4.363v-1.363H.813v1.363Zm0-2h4.363V8.293H.813v1.363Z" fill="url(#c)"/><defs><linearGradient id="a" x1="-1.666" y1=".682" x2="13.393" y2=".682" gradientUnits="userSpaceOnUse"><stop stop-color="#429B72"/><stop offset="1" stop-color="#333" stop-opacity=".6"/></linearGradient><linearGradient id="b" x1="-1.666" y1=".682" x2="13.393" y2=".682" gradientUnits="userSpaceOnUse"><stop stop-color="#429B72"/><stop offset="1" stop-color="#333" stop-opacity=".6"/></linearGradient><linearGradient id="c" x1="-1.666" y1=".682" x2="13.393" y2=".682" gradientUnits="userSpaceOnUse"><stop stop-color="#429B72"/><stop offset="1" stop-color="#333" stop-opacity=".6"/></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2202" height="2476"><defs><linearGradient id="b" x1="22.367%" x2="87.411%" y1="78.665%" y2="25.105%"><stop offset="0%" stop-color="#ADADAD" stop-opacity=".5"/><stop offset="100%" stop-opacity=".5"/></linearGradient><path id="a" d="M981.153 35.355c78.66-46.107 154.6-47.464 233.93-2.033 292.92 168.838 586.52 337.673 879.45 506.511 35.25 20.342 61.7 46.108 79.33 77.297l.546.938c17.94 31.001 26.574 67.962 26.574 108.909v1021.274c-.11 40.36-8.92 76.03-25.77 107.02-17.62 32.54-44.07 58.99-80.68 80.01-292.93 168.84-586.53 337-879.45 506.51-72.65 42.58-143.56 44.1-215.94 7.76-6.43-3.19-12.88-6.68-19.35-10.47-116.62-69.16-700.431-403.45-874.693-504.48-35.937-21.02-63.059-48.14-80.691-80.01l.756-.44C8.606 1823.221 0 1787.661 0 1747.461V727.656c0-82.723 34.58-146.462 106.456-187.823C280.039 438.801 864.523 103.841 981.153 35.355Zm117.98 470.575c-404.121 0-730.944 327.503-730.944 730.951 0 132.22 35.257 256.98 96.962 364.12 126.118 219.01 363.44 366.83 634.662 366.83 271.91 0 509.23-148.5 635.34-368.87l-318.01-183.75c-62.38 110.52-181.04 185.79-317.33 185.79-135.61 0-253.591-73.91-315.973-183.76-30.513-53.56-48.144-114.59-48.144-180.36 0-201.39 162.737-364.121 364.117-364.121 134.26 0 251.56 73.23 314.62 181.721l318.01-183.078c-126.79-218.333-362.76-365.473-633.31-365.473Zm720.1 558.041h-72.55v58.99h-58.31v72.56h58.31v87.47h-58.31v72.55h58.31v58.99h72.55l-.001-58.99h88.15l.001 58.99h72.55v-58.99h58.99v-72.55h-58.99v-87.47h58.99v-72.56h-58.99v-58.99h-72.55l-.001 58.99h-88.15l.001-58.99Zm88.149 131.55v87.47h-88.15v-87.47h88.15Z"/></defs><g fill="none" fill-rule="nonzero" transform="translate(.507 .109)"><use xlink:href="#a" fill="#4C4C4C"/><use xlink:href="#a" fill="url(#b)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 2500 2500" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M174.672,1866.26L173.916,1866.7C191.548,1898.57 218.67,1925.69 254.607,1946.71C428.869,2047.74 1012.68,2382.03 1129.3,2451.19C1135.77,2454.98 1142.22,2458.47 1148.65,2461.66C1221.03,2498 1291.94,2496.48 1364.59,2453.9C1657.51,2284.39 1951.11,2116.23 2244.04,1947.39C2280.65,1926.37 2307.1,1899.92 2324.72,1867.38C2341.57,1836.39 2350.38,1800.72 2350.49,1760.36C2350.49,1759.87 2350.49,1759.38 2350.49,1758.89L2350.49,739.086C2350.49,697.725 2341.68,660.432 2323.37,629.239L2323.37,629.241L2323.37,629.239C2305.74,598.05 2279.29,572.284 2244.04,551.942C1951.11,383.104 1657.51,214.269 1364.59,45.431C1285.26,0 1209.32,1.357 1130.66,47.464C1014.03,115.95 429.546,450.91 255.963,551.942C184.087,593.303 149.507,657.042 149.507,739.765L149.507,1759.57C149.507,1799.77 158.113,1835.33 174.672,1866.26Z" style="fill:white;fill-rule:nonzero;"/>
<path d="M1566.65,1427.32C1504.27,1537.84 1385.61,1613.11 1249.32,1613.11C1113.71,1613.11 995.729,1539.2 933.347,1429.35C902.834,1375.79 885.203,1314.76 885.203,1248.99C885.203,1047.6 1047.94,884.869 1249.32,884.869C1383.58,884.869 1500.88,958.099 1563.94,1066.59L1881.95,883.512C1755.16,665.179 1519.19,518.039 1248.64,518.039C844.519,518.039 517.696,845.542 517.696,1248.99C517.696,1381.21 552.953,1505.97 614.658,1613.11C740.776,1832.12 978.098,1979.94 1249.32,1979.94C1521.23,1979.94 1758.55,1831.44 1884.66,1611.07L1566.65,1427.32ZM1968.74,1076.08L1896.19,1076.08L1896.19,1426.64L1968.74,1426.64L1968.74,1076.08ZM2129.44,1076.08L2056.89,1076.08L2056.89,1426.64L2129.44,1426.64L2129.44,1076.08Z" style="fill-rule:nonzero;"/>
<g>
<path d="M2188.43,1135.07L1837.88,1135.07L1837.88,1207.63L2188.43,1207.63L2188.43,1135.07ZM2188.43,1295.1L1837.88,1295.1L1837.88,1367.65L2188.43,1367.65L2188.43,1295.1Z" style="fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,9 @@
<svg width="81" height="80" viewBox="0 0 81 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M62.8209 10.9141L44.7971 10.9339L15.7344 39.9967L24.701 48.9743L32.5819 41.1376L62.8209 10.9141ZM45.3142 37.7255C45.0585 37.7136 44.7948 37.7015 44.6294 37.92L29.1317 53.4154L38.0337 62.2629L38.0234 62.273L44.394 68.649C44.4358 68.6861 44.4773 68.7276 44.5196 68.7698C44.6939 68.944 44.881 69.1309 45.151 69.0705C48.0938 69.0639 51.0367 69.065 53.9798 69.0661C56.9231 69.0672 59.8666 69.0683 62.8106 69.0617L47.1433 53.3803L62.8055 37.728L45.6203 37.7235C45.5247 37.7352 45.4201 37.7303 45.3142 37.7255Z" fill="url(#paint0_linear_1785_15060)"/>
<defs>
<linearGradient id="paint0_linear_1785_15060" x1="15.7344" y1="69.0821" x2="72.627" y2="23.028" gradientUnits="userSpaceOnUse">
<stop stop-color="#171717" stop-opacity="0.5"/>
<stop offset="1"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 925 B

@@ -0,0 +1,9 @@
<svg width="81" height="80" viewBox="0 0 81 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M62.8209 10.9141L44.7971 10.9339L15.7344 39.9967L24.701 48.9743L32.5819 41.1376L62.8209 10.9141ZM45.3142 37.7255C45.0585 37.7136 44.7948 37.7015 44.6294 37.92L29.1317 53.4154L38.0337 62.2629L38.0234 62.273L44.394 68.649C44.4358 68.6861 44.4773 68.7276 44.5196 68.7698C44.6939 68.944 44.881 69.1309 45.151 69.0705C48.0938 69.0639 51.0367 69.065 53.9798 69.0661C56.9231 69.0672 59.8666 69.0683 62.8106 69.0617L47.1433 53.3803L62.8055 37.728L45.6203 37.7235C45.5247 37.7352 45.4201 37.7303 45.3142 37.7255Z" fill="url(#paint0_linear_1785_15060)"/>
<defs>
<linearGradient id="paint0_linear_1785_15060" x1="15.7344" y1="69.0821" x2="72.627" y2="23.028" gradientUnits="userSpaceOnUse">
<stop stop-color="#EDEDED" stop-opacity="0.5"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 944 B

@@ -0,0 +1,9 @@
<svg width="81" height="80" viewBox="0 0 81 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.7305 13.2305H67.2695V66.7695H13.7305V13.2305ZM54.6588 58.1144C52.1773 58.1144 50.7743 56.8201 49.6959 55.0593L45.6073 57.4345C47.0843 60.3528 50.1029 62.5794 54.7753 62.5794C59.5538 62.5794 63.1121 60.0979 63.1121 55.5683C63.1121 51.3668 60.6986 49.498 56.4239 47.6649L55.1662 47.1261C53.0076 46.1913 52.0728 45.5803 52.0728 44.071C52.0728 42.8498 53.0068 41.915 54.4804 41.915C55.9251 41.915 56.8556 42.5243 57.7182 44.071L61.6359 41.5555C59.9788 38.6406 57.6791 37.5273 54.4804 37.5273C49.9873 37.5273 47.1124 40.3998 47.1124 44.173C47.1124 48.2691 49.5242 50.2068 53.1546 51.7534L54.4124 52.2931C56.7069 53.2967 58.0751 53.9077 58.0751 55.6329C58.0751 57.0725 56.7435 58.1144 54.6588 58.1144ZM35.1613 58.0829C33.4327 58.0829 32.7138 56.8974 31.9234 55.4952L27.8281 57.9746C29.0145 60.4854 31.3473 62.57 35.3754 62.57C39.8336 62.57 42.8879 60.199 42.8879 54.9896V37.8146H37.8569V54.9216C37.8569 57.4362 36.8142 58.0829 35.1613 58.0829Z" fill="url(#paint0_linear_1785_15055)"/>
<defs>
<linearGradient id="paint0_linear_1785_15055" x1="13.7305" y1="66.7695" x2="67.2695" y2="13.2305" gradientUnits="userSpaceOnUse">
<stop stop-color="#171717" stop-opacity="0.5"/>
<stop offset="1"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,9 @@
<svg width="81" height="80" viewBox="0 0 81 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.7305 13.2305H67.2695V66.7695H13.7305V13.2305ZM54.6588 58.1144C52.1773 58.1144 50.7743 56.8201 49.6959 55.0593L45.6073 57.4345C47.0843 60.3528 50.1029 62.5794 54.7753 62.5794C59.5538 62.5794 63.1121 60.0979 63.1121 55.5683C63.1121 51.3668 60.6986 49.498 56.4239 47.6649L55.1662 47.1261C53.0076 46.1913 52.0728 45.5803 52.0728 44.071C52.0728 42.8498 53.0068 41.915 54.4804 41.915C55.9251 41.915 56.8556 42.5243 57.7182 44.071L61.6359 41.5555C59.9788 38.6406 57.6791 37.5273 54.4804 37.5273C49.9873 37.5273 47.1124 40.3998 47.1124 44.173C47.1124 48.2691 49.5242 50.2068 53.1546 51.7534L54.4124 52.2931C56.7069 53.2967 58.0751 53.9077 58.0751 55.6329C58.0751 57.0725 56.7435 58.1144 54.6588 58.1144ZM35.1613 58.0829C33.4327 58.0829 32.7138 56.8974 31.9234 55.4952L27.8281 57.9746C29.0145 60.4854 31.3473 62.57 35.3754 62.57C39.8336 62.57 42.8879 60.199 42.8879 54.9896V37.8146H37.8569V54.9216C37.8569 57.4362 36.8142 58.0829 35.1613 58.0829Z" fill="url(#paint0_linear_1785_15055)"/>
<defs>
<linearGradient id="paint0_linear_1785_15055" x1="13.7305" y1="66.7695" x2="67.2695" y2="13.2305" gradientUnits="userSpaceOnUse">
<stop stop-color="#EDEDED" stop-opacity="0.5"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,9 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.7668 10.4216C34.8343 10.0558 37.1841 9.84591 39.6447 9.83448C42.1052 9.82305 44.6707 10.01 47.1433 10.4216C51.0493 11.0726 54.3399 14.0028 54.3399 17.9034V31.6089C54.3399 35.628 51.1452 38.923 47.1433 38.923H32.7668C27.8856 38.923 23.7752 43.1136 23.7752 47.8643V54.4403H18.8264C14.6433 54.4403 12.2 51.4041 11.1768 47.143C9.79667 41.4184 9.85529 37.9973 11.1768 32.5148C12.3225 27.7318 15.9853 25.2175 20.1684 25.2175H25.5701H39.9634V23.389H25.5701V17.9034C25.5701 13.7499 26.676 11.4977 32.7668 10.4216ZM34.5617 16.9976C34.5617 15.4804 33.3476 14.2464 31.8609 14.2464C30.3688 14.2464 29.1601 15.4804 29.1601 16.9976C29.1601 18.5094 30.3688 19.732 31.8609 19.732C33.3476 19.732 34.5617 18.5094 34.5617 16.9976ZM56.1348 31.6092V25.2178H61.5365C65.7249 25.2178 67.6994 28.3508 68.7332 32.5151C70.1719 38.2988 70.2359 42.6346 68.7332 47.1432C67.2784 51.5227 65.7196 54.4405 61.5365 54.4405H54.3399H39.9634V56.269H54.3399V61.7546C54.3399 65.9081 50.7668 68.0195 47.1432 69.0687C41.6919 70.6505 37.3228 70.4083 32.7667 69.0687C28.962 67.9496 25.5701 65.6553 25.5701 61.7546V48.0491C25.5701 44.1054 28.8287 40.735 32.7667 40.735H47.1432C51.9338 40.735 56.1348 36.5644 56.1348 31.6092ZM50.7499 62.6605C50.7499 61.1486 49.5412 59.9261 48.0491 59.9261C46.5624 59.9261 45.3483 61.1486 45.3483 62.6605C45.3483 64.1777 46.5624 65.4116 48.0491 65.4116C49.5412 65.4116 50.7499 64.1777 50.7499 62.6605Z" fill="url(#paint0_linear_1785_15271)"/>
<defs>
<linearGradient id="paint0_linear_1785_15271" x1="10.1636" y1="70.1661" x2="70.492" y2="10.4969" gradientUnits="userSpaceOnUse">
<stop stop-color="#171717" stop-opacity="0.5"/>
<stop offset="1"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,9 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.7668 10.4216C34.8343 10.0558 37.1841 9.84591 39.6447 9.83448C42.1052 9.82305 44.6707 10.01 47.1433 10.4216C51.0493 11.0726 54.3399 14.0028 54.3399 17.9034V31.6089C54.3399 35.628 51.1452 38.923 47.1433 38.923H32.7668C27.8856 38.923 23.7752 43.1136 23.7752 47.8643V54.4403H18.8264C14.6433 54.4403 12.2 51.4041 11.1768 47.143C9.79667 41.4184 9.85529 37.9973 11.1768 32.5148C12.3225 27.7318 15.9853 25.2175 20.1684 25.2175H25.5701H39.9634V23.389H25.5701V17.9034C25.5701 13.7499 26.676 11.4977 32.7668 10.4216ZM34.5617 16.9976C34.5617 15.4804 33.3476 14.2464 31.8609 14.2464C30.3688 14.2464 29.1601 15.4804 29.1601 16.9976C29.1601 18.5094 30.3688 19.732 31.8609 19.732C33.3476 19.732 34.5617 18.5094 34.5617 16.9976ZM56.1348 31.6092V25.2178H61.5365C65.7249 25.2178 67.6994 28.3508 68.7332 32.5151C70.1719 38.2988 70.2359 42.6346 68.7332 47.1432C67.2784 51.5227 65.7196 54.4405 61.5365 54.4405H54.3399H39.9634V56.269H54.3399V61.7546C54.3399 65.9081 50.7668 68.0195 47.1432 69.0687C41.6919 70.6505 37.3228 70.4083 32.7667 69.0687C28.962 67.9496 25.5701 65.6553 25.5701 61.7546V48.0491C25.5701 44.1054 28.8287 40.735 32.7667 40.735H47.1432C51.9338 40.735 56.1348 36.5644 56.1348 31.6092ZM50.7499 62.6605C50.7499 61.1486 49.5412 59.9261 48.0491 59.9261C46.5624 59.9261 45.3483 61.1486 45.3483 62.6605C45.3483 64.1777 46.5624 65.4116 48.0491 65.4116C49.5412 65.4116 50.7499 64.1777 50.7499 62.6605Z" fill="url(#paint0_linear_1785_15271)"/>
<defs>
<linearGradient id="paint0_linear_1785_15271" x1="10.1636" y1="70.1661" x2="70.492" y2="10.4969" gradientUnits="userSpaceOnUse">
<stop stop-color="#EDEDED" stop-opacity="0.5"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+35 -2
View File
@@ -224,10 +224,40 @@
@apply border-b flex border-scale-500;
}
&.Docs--table-editor .doc-section--introduction {
.text {
width: 85%;
}
}
&.Docs--table-editor .doc-section--introduction {
.text {
width: 85%;
}
}
&.Docs--table-editor .doc-section--client-libraries {
flex-direction: column;
margin-bottom: 40px;
.text,
.code {
width: 100%;
max-width: none;
}
.code {
margin-bottom: 20px;
}
}
.doc-section article:first-child {
@apply prose prose-docs prose-sm max-w-none;
}
&.Docs--table-editor .doc-section__table-name {
@apply mt-12;
}
.text {
@apply p-4 px-6 w-1/2;
max-width: 50%;
@@ -239,7 +269,7 @@
a {
@apply transition;
}
a:hover {
@apply text-brand-1000;
}
@@ -270,6 +300,9 @@
@apply text-brand-900;
}
}
.codeblock-container + .codeblock-container {
@apply mt-12;
}
}
::-moz-selection {
@@ -339,4 +372,4 @@
label {
@apply mt-1;
}
}
}