diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index 772f434d345..3edb9ec62c3 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -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' diff --git a/packages/ui/internals/icons.js b/packages/ui/internals/icons.js index 07a7f0e5539..1cde50bfbd9 100644 --- a/packages/ui/internals/icons.js +++ b/packages/ui/internals/icons.js @@ -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' -} \ No newline at end of file + IconZoomOut: 'src/components/Icon/icons/IconZoomOut/index.tsx', +} diff --git a/packages/ui/internals/icons.json b/packages/ui/internals/icons.json index 992be561d20..a348ede4cf5 100644 --- a/packages/ui/internals/icons.json +++ b/packages/ui/internals/icons.json @@ -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" diff --git a/packages/ui/src/components/Icon/IconImportHandler.tsx b/packages/ui/src/components/Icon/IconImportHandler.tsx index 0d158b428c6..3863d419700 100644 --- a/packages/ui/src/components/Icon/IconImportHandler.tsx +++ b/packages/ui/src/components/Icon/IconImportHandler.tsx @@ -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`) diff --git a/packages/ui/src/components/Icon/icons/IconTable/IconTable.tsx b/packages/ui/src/components/Icon/icons/IconTable/IconTable.tsx new file mode 100644 index 00000000000..c0e41b29099 --- /dev/null +++ b/packages/ui/src/components/Icon/icons/IconTable/IconTable.tsx @@ -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 + } + + export default IconTable + \ No newline at end of file diff --git a/packages/ui/src/components/Icon/icons/IconTable/index.tsx b/packages/ui/src/components/Icon/icons/IconTable/index.tsx new file mode 100644 index 00000000000..906c04de230 --- /dev/null +++ b/packages/ui/src/components/Icon/icons/IconTable/index.tsx @@ -0,0 +1 @@ +export { default as IconTable } from './IconTable' \ No newline at end of file diff --git a/packages/ui/src/components/Icon/icons/index.tsx b/packages/ui/src/components/Icon/icons/index.tsx index 9fc2134a5a7..5975707ac72 100644 --- a/packages/ui/src/components/Icon/icons/index.tsx +++ b/packages/ui/src/components/Icon/icons/index.tsx @@ -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' diff --git a/packages/ui/src/components/SidePanel/SidePanel.tsx b/packages/ui/src/components/SidePanel/SidePanel.tsx index d04b062b3c2..b9595e7fec3 100644 --- a/packages/ui/src/components/SidePanel/SidePanel.tsx +++ b/packages/ui/src/components/SidePanel/SidePanel.tsx @@ -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 diff --git a/packages/ui/src/lib/theme/defaultTheme.ts b/packages/ui/src/lib/theme/defaultTheme.ts index 28fe703fb73..4617d5a8e94 100644 --- a/packages/ui/src/lib/theme/defaultTheme.ts +++ b/packages/ui/src/lib/theme/defaultTheme.ts @@ -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: ` diff --git a/studio/components/grid/types/grid.ts b/studio/components/grid/types/grid.ts index a036249a97d..bae5c96556b 100644 --- a/studio/components/grid/types/grid.ts +++ b/studio/components/grid/types/grid.ts @@ -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 } diff --git a/studio/components/interfaces/Docs/Docs.types.ts b/studio/components/interfaces/Docs/Docs.types.ts new file mode 100644 index 00000000000..e6f3e8cb322 --- /dev/null +++ b/studio/components/interfaces/Docs/Docs.types.ts @@ -0,0 +1,4 @@ +export type showApiKey = { + key: string + name: string +} \ No newline at end of file diff --git a/studio/components/interfaces/Docs/GeneratingTypes.tsx b/studio/components/interfaces/Docs/GeneratingTypes.tsx new file mode 100644 index 00000000000..9cdabfe4ecc --- /dev/null +++ b/studio/components/interfaces/Docs/GeneratingTypes.tsx @@ -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 ( + <> +

+ Generating types + + + + + +

+
+
+

+ Supabase APIs are generated from your database, which means that we can use database + introspection to generate type-safe API definitions. +

+

+ You can generate types from your database either through the{' '} + + Supabase CLI + + , or by downloading the types file via the button on the right and importing it in your + application within src/index.ts. +

+
+
+
+

+ {selectedLang === 'js' && ( + + )} +

+

+ Remember to re-generate and download this file as you make changes to your tables. +

+
+ + +
+
+ + ) +} + +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 +`, + }, + }), +} diff --git a/studio/components/interfaces/Docs/LangSelector.tsx b/studio/components/interfaces/Docs/LangSelector.tsx new file mode 100644 index 00000000000..a0e05272874 --- /dev/null +++ b/studio/components/interfaces/Docs/LangSelector.tsx @@ -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 = ({ + selectedLang, + setSelectedLang, + showApiKey, + setShowApiKey, + apiKey, + autoApiService, +}) => { + const canReadServiceKey = checkPermissions( + PermissionAction.READ, + 'service_api_keys.service_role_key' + ) + + return ( +
+
+ + + {selectedLang == 'bash' && ( +
+
+ + Project API key : +
+ + setShowApiKey(DEFAULT_KEY)}> + hide + + {apiKey && ( + + setShowApiKey({ + key: apiKey, + name: 'anon (public)', + }) + } + > + anon (public) + + )} + {canReadServiceKey && ( + + setShowApiKey({ + key: autoApiService.serviceApiKey, + name: 'service_role (secret)', + }) + } + > + service_role (secret) + + )} + + } + > + + +
+ )} +
+
+ ) +} + +export default LangSelector diff --git a/studio/components/interfaces/Docs/ResourceContent.tsx b/studio/components/interfaces/Docs/ResourceContent.tsx index a37ed884081..21e6a3dc5fb 100644 --- a/studio/components/interfaces/Docs/ResourceContent.tsx +++ b/studio/components/interfaces/Docs/ResourceContent.tsx @@ -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 ( <> -

- {resourceId} +

+ + + + {resourceId}

-
+
+ {properties.map((x) => (
-
+
-
-
+
+
-
+
))} @@ -87,8 +94,8 @@ const ResourceContent = ({ To read rows in {resourceId}, use the select method.

- - Learn more. + + Learn more

@@ -120,8 +127,11 @@ const ResourceContent = ({

Filtering

Supabase provides a wide range of filters.

- - Learn more. + + Learn more

@@ -147,8 +157,8 @@ const ResourceContent = ({ insert will also return the replaced values for UPSERT.

- - Learn more. + + Learn more

@@ -183,8 +193,8 @@ const ResourceContent = ({ update will also return the replaced values for UPDATE.

- - Learn more. + + Learn more

@@ -207,8 +217,8 @@ const ResourceContent = ({ default, so remember to specify your filters!

- - Learn more. + + Learn more

@@ -230,8 +240,8 @@ const ResourceContent = ({ users depending on Row Level Security (RLS) policies.

- - Learn more. + + Learn more

@@ -264,21 +274,6 @@ const ResourceContent = ({ - <> -

Much more

-
-
-

- These docs are a work in progress! See our{' '} - - docs - {' '} - for the additional functionality Supabase has to offer. -

-
-
-
- ) } diff --git a/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx b/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx index 2a8a757aaa5..9996fc88488 100644 --- a/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx +++ b/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx @@ -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 = ({ table }) => { +const GridHeaderActions: FC = ({ + 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 = ({ table }) => { !checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') && !checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns') + function handlePreviewToggle() { + setApiPreviewPanelOpen(!apiPreviewPanelOpen) + refreshDocs() + } + + const RenderAPIPreviewToggle = () => { + return ( + + ) + } + return (
{isReadOnly && ( @@ -44,6 +70,9 @@ const GridHeaderActions: FC = ({ table }) => { )} +
+ +
-

- Generating types - - - - - -

-
-
-

- Supabase APIs are generated from your database, which means that we can use database - introspection to generate type-safe API definitions. -

-

- You can generate types from your database either through the{' '} - Supabase CLI, - or by downloading the types file via the button on the right and importing it in your - application within src/index.ts. -

-
-
- {selectedLang === 'js' && ( - - )} - - -
-
+

GraphQL vs Supabase @@ -141,22 +69,6 @@ const Introduction: FC = ({ 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: { diff --git a/studio/components/to-be-cleaned/Docs/Param.js b/studio/components/to-be-cleaned/Docs/Param.js deleted file mode 100644 index 9abb36712f8..00000000000 --- a/studio/components/to-be-cleaned/Docs/Param.js +++ /dev/null @@ -1,35 +0,0 @@ -import { Badge } from 'ui' -import Description from './Description' - -const Param = ({ - name, - type, - format, - required, - description, - metadata = {}, - onDesciptionUpdated = () => {}, -}) => { - return ( - <> -
- {name} - {required ? 'Required' : 'Optional'} -
- {format && format != type && ( -
- - {format} - {type} -
- )} - {description !== false && ( - <> - - - - )} - - ) -} -export default Param diff --git a/studio/components/to-be-cleaned/Docs/Param.tsx b/studio/components/to-be-cleaned/Docs/Param.tsx new file mode 100644 index 00000000000..611a61e707a --- /dev/null +++ b/studio/components/to-be-cleaned/Docs/Param.tsx @@ -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 = ({ + name, + type, + format, + required, + description, + metadata = {}, + onDesciptionUpdated = () => {}, +}) => { + return ( + <> +
+
+
+ + +
+ {name} +
+
+
+ + {required ? 'Required' : 'Optional'} +
+ {format && ( +
+
+ +
+ + + + {getColumnType(type, format)} + + +
+
+
+ +
+ + + + {format} + + +
+
+
+ )} + {description !== false && ( +
+ + +
+ )} + + ) +} +export default Param diff --git a/studio/pages/project/[ref]/api/index.tsx b/studio/pages/project/[ref]/api/index.tsx index 82bed5db772..5f4e22b9031 100644 --- a/studio/pages/project/[ref]/api/index.tsx +++ b/studio/pages/project/[ref]/api/index.tsx @@ -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 = 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 = observer(({}) => { if (error || jsonSchemaError) return ( -
+

Error connecting to API

{`${error || jsonSchemaError}`}

@@ -113,7 +108,7 @@ const DocView: FC = observer(({}) => { ) if (!data || !jsonSchema || !PageState.jsonSchema) return ( -
+

Building docs ...

) @@ -129,81 +124,17 @@ const DocView: FC = observer(({}) => { const PAGE_KEY: any = resource || rpc || page || 'index' return ( -
+
-
-
-
- - - {selectedLang == 'bash' && ( -
-
- - Project API key : -
- - setShowApiKey(DEFAULT_KEY)}> - hide - - - setShowApiKey({ - key: anonKey, - name: 'anon (public)', - }) - } - > - anon (public) - - {canReadServiceKey && ( - - setShowApiKey({ - key: autoApiService.serviceApiKey, - name: 'service_role (secret)', - }) - } - > - service_role (secret) - - )} - - } - > - - -
- )} -
-
+
+
{resource ? ( diff --git a/studio/public/img/icons/reference-auth-light.svg b/studio/public/img/icons/reference-auth-light.svg new file mode 100644 index 00000000000..3995c790842 --- /dev/null +++ b/studio/public/img/icons/reference-auth-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/studio/public/img/icons/reference-csharp-light.svg b/studio/public/img/icons/reference-csharp-light.svg new file mode 100644 index 00000000000..782f87bcc40 --- /dev/null +++ b/studio/public/img/icons/reference-csharp-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/studio/public/img/icons/reference-csharp.svg b/studio/public/img/icons/reference-csharp.svg new file mode 100644 index 00000000000..40d0375079d --- /dev/null +++ b/studio/public/img/icons/reference-csharp.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/studio/public/img/icons/reference-dart-light.svg b/studio/public/img/icons/reference-dart-light.svg new file mode 100644 index 00000000000..178f51a5b88 --- /dev/null +++ b/studio/public/img/icons/reference-dart-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/studio/public/img/icons/reference-dart.svg b/studio/public/img/icons/reference-dart.svg new file mode 100644 index 00000000000..33f9ad24a27 --- /dev/null +++ b/studio/public/img/icons/reference-dart.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/studio/public/img/icons/reference-javascript-light.svg b/studio/public/img/icons/reference-javascript-light.svg new file mode 100644 index 00000000000..a11845dff91 --- /dev/null +++ b/studio/public/img/icons/reference-javascript-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/studio/public/img/icons/reference-javascript.svg b/studio/public/img/icons/reference-javascript.svg new file mode 100644 index 00000000000..f8ce1de0403 --- /dev/null +++ b/studio/public/img/icons/reference-javascript.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/studio/public/img/icons/reference-python-light.svg b/studio/public/img/icons/reference-python-light.svg new file mode 100644 index 00000000000..1ac57aae572 --- /dev/null +++ b/studio/public/img/icons/reference-python-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/studio/public/img/icons/reference-python.svg b/studio/public/img/icons/reference-python.svg new file mode 100644 index 00000000000..4ca064b0b73 --- /dev/null +++ b/studio/public/img/icons/reference-python.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/studio/styles/ui.scss b/studio/styles/ui.scss index e927eb48782..e60672668f8 100644 --- a/studio/styles/ui.scss +++ b/studio/styles/ui.scss @@ -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; } -} \ No newline at end of file +}