diff --git a/.cursor/rules/studio-ui.mdc b/.cursor/rules/studio-ui.mdc index c6f4888152547..391978294b4bb 100644 --- a/.cursor/rules/studio-ui.mdc +++ b/.cursor/rules/studio-ui.mdc @@ -42,6 +42,7 @@ We use Tailwind for styling. - 'text-warning' for calling out information that needs action - 'text-destructive' for calling out when something went wrong - When needing to apply typography styles, read @apps/studio/styles/typography.scss and use one of the available classes instead of hard coding classes e.g. use "heading-default" instead of "text-sm font-medium" +- When applying focus styles for keyboard navigation, read @apps/studio/styles/focus.scss for any appropriate classes for consistency with other focus styles ## Page structure diff --git a/apps/design-system/config/docs.ts b/apps/design-system/config/docs.ts index 93f3665c871ea..775c8817e8e63 100644 --- a/apps/design-system/config/docs.ts +++ b/apps/design-system/config/docs.ts @@ -35,6 +35,11 @@ export const docsConfig: DocsConfig = { href: '/docs/icons', items: [], }, + { + items: [], + href: '/docs/ui-patterns/accessibility', + title: 'Accessibility', + }, ], }, { diff --git a/apps/design-system/content/docs/ui-patterns/accessibility.mdx b/apps/design-system/content/docs/ui-patterns/accessibility.mdx new file mode 100644 index 0000000000000..267d8089138de --- /dev/null +++ b/apps/design-system/content/docs/ui-patterns/accessibility.mdx @@ -0,0 +1,50 @@ +--- +title: Accessibility +description: Make Supabase work for everyone. +--- + +Accessibility is about making an interface work for as many people as possible across as many circumstances as possible. All of us lean on affordances that accessible experiences provide: + +- Keyboard navigation +- Legible and resizable elements +- Large tap targets +- Clear and simple language + +## Checklist + +About to push some code? At a minimum, check your work against this list: + +- Are interactive page elements [keyboard-focusable](#focus-management)? +- Are all elements announcable by a [screen reader](#screen-reader-support)? +- Are textual elements legible and scalable? +- Can I use this on a smaller and/or older device? + +## Focus management + +All interactive page elements should be reachable by keyboard. They should also provide visual feedback upon selection via a `focus-visible` state. We use consistent focus styles such as `inset-focus` so users recognize this state instantly. + +```jsx + +

{name}

+ +
+``` + +Consider also affordances like `ctrl` and `meta` key support for opening in a new tab. Anything that you can do with a mouse input should be replicable by keyboard. + +## Screen reader support + +Textual elements are supported out-of-the-box by screen readers. Imagery of course should be described by `alt` tags. + +Less obvious however are scaffolding elements that only makes sense visually, when paired with other content. For example: a table column for actions may not have a visual _Actions_ label because its purpose is obvious to a sighted person. For everyone else’s sake, this column should be titled with `sr-only` text: + +```jsx + + Actions + +``` diff --git a/apps/studio/components/interfaces/Storage/AnalyticsBuckets/DeleteAnalyticsBucketModal.tsx b/apps/studio/components/interfaces/Storage/AnalyticsBuckets/DeleteAnalyticsBucketModal.tsx index dc91ae53721f1..2c3d539e94741 100644 --- a/apps/studio/components/interfaces/Storage/AnalyticsBuckets/DeleteAnalyticsBucketModal.tsx +++ b/apps/studio/components/interfaces/Storage/AnalyticsBuckets/DeleteAnalyticsBucketModal.tsx @@ -64,7 +64,7 @@ export const DeleteAnalyticsBucketModal = ({ visible={visible} size="medium" variant="destructive" - title={`Confirm deletion of ${bucketId}`} + title={`Delete bucket “${bucketId}”`} loading={isDeleting} confirmPlaceholder="Type bucket name" confirmString={bucketId ?? ''} diff --git a/apps/studio/components/interfaces/Storage/AnalyticsBuckets/index.tsx b/apps/studio/components/interfaces/Storage/AnalyticsBuckets/index.tsx index 1074f3330807a..1b1a77964ac77 100644 --- a/apps/studio/components/interfaces/Storage/AnalyticsBuckets/index.tsx +++ b/apps/studio/components/interfaces/Storage/AnalyticsBuckets/index.tsx @@ -1,37 +1,23 @@ -import { ExternalLink, MoreVertical, Search, Trash2 } from 'lucide-react' -import Link from 'next/link' +import { ChevronRight, ExternalLink, Search } from 'lucide-react' +import { useRouter } from 'next/navigation' import { useState } from 'react' import { useParams } from 'common' import { ScaffoldHeader, ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' -import { AnalyticsBucket, useAnalyticsBucketsQuery } from 'data/storage/analytics-buckets-query' -import { - Button, - Card, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from 'ui' +import { useAnalyticsBucketsQuery } from 'data/storage/analytics-buckets-query' +import { Bucket as BucketIcon } from 'icons' +import { Button, Card, cn, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui' import { Admonition, TimestampInfo } from 'ui-patterns' import { Input } from 'ui-patterns/DataInputs/Input' import { EmptyBucketState } from '../EmptyBucketState' import { CreateAnalyticsBucketModal } from './CreateAnalyticsBucketModal' -import { DeleteAnalyticsBucketModal } from './DeleteAnalyticsBucketModal' export const AnalyticsBuckets = () => { const { ref } = useParams() + const router = useRouter() const [filterString, setFilterString] = useState('') - const [selectedBucket, setSelectedBucket] = useState() - const [modal, setModal] = useState<'edit' | 'empty' | 'delete' | null>(null) const { data: buckets = [], isLoading: isLoadingBuckets } = useAnalyticsBucketsQuery({ projectRef: ref, @@ -41,13 +27,25 @@ export const AnalyticsBuckets = () => { filterString.length === 0 ? true : bucket.id.toLowerCase().includes(filterString.toLowerCase()) ) + const handleBucketNavigation = ( + bucketId: string, + event: React.MouseEvent | React.KeyboardEvent + ) => { + const url = `/project/${ref}/storage/analytics/buckets/${encodeURIComponent(bucketId)}` + if (event.metaKey || event.ctrlKey) { + window.open(url, '_blank') + } else { + router.push(url) + } + } + return ( }> { rel="noopener noreferrer" href="https://github.com/orgs/supabase/discussions/40116" > - Leave feedback + Share feedback } > -

- Expect rapid changes, limited features, and possible breaking updates as we expand access. +

+ Analytics buckets are now in private alpha. Expect rapid changes, limited features, and + possible breaking updates. Please share feedback as we refine the experience and expand + access.

-

Please share feedback as we refine the experience!

{!isLoadingBuckets && @@ -94,9 +93,16 @@ export const AnalyticsBuckets = () => { + {analyticsBuckets.length > 0 && ( + + Icon + + )} Name Created at - + + Actions + @@ -111,15 +117,18 @@ export const AnalyticsBuckets = () => { )} {analyticsBuckets.map((bucket) => ( - + + + + - {bucket.id}

+
@@ -132,31 +141,8 @@ export const AnalyticsBuckets = () => { -
- - - -
)} - - {selectedBucket && ( - setModal(null)} - /> - )} ) } diff --git a/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx b/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx index f816ff023922e..11c3ebbf41166 100644 --- a/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx +++ b/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx @@ -229,7 +229,7 @@ export const CreateBucketModal = ({ - Create a storage bucket + Create file bucket diff --git a/apps/studio/components/interfaces/Storage/DeleteBucketModal.tsx b/apps/studio/components/interfaces/Storage/DeleteBucketModal.tsx index 206489e89197d..7e92b014f6ea7 100644 --- a/apps/studio/components/interfaces/Storage/DeleteBucketModal.tsx +++ b/apps/studio/components/interfaces/Storage/DeleteBucketModal.tsx @@ -83,7 +83,7 @@ export const DeleteBucketModal = ({ visible, bucket, onClose }: DeleteBucketModa visible={visible} size="medium" variant="destructive" - title={`Confirm deletion of ${bucket.id}`} + title={`Delete bucket “${bucket.id}”`} loading={isDeletingBucket || isDeletingPolicies} confirmPlaceholder="Type bucket name" confirmString={bucket.id} diff --git a/apps/studio/components/interfaces/Storage/EditBucketModal.tsx b/apps/studio/components/interfaces/Storage/EditBucketModal.tsx index c70e00e021dbd..43b7e7849e2c7 100644 --- a/apps/studio/components/interfaces/Storage/EditBucketModal.tsx +++ b/apps/studio/components/interfaces/Storage/EditBucketModal.tsx @@ -197,7 +197,7 @@ export const EditBucketModal = ({ visible, bucket, onClose }: EditBucketModalPro > - {`Edit bucket "${bucket?.name}"`} + {`Edit bucket “${bucket?.name}”`} diff --git a/apps/studio/components/interfaces/Storage/EmptyBucketModal.tsx b/apps/studio/components/interfaces/Storage/EmptyBucketModal.tsx index 597ae9ea7c56d..fe7d4849c6919 100644 --- a/apps/studio/components/interfaces/Storage/EmptyBucketModal.tsx +++ b/apps/studio/components/interfaces/Storage/EmptyBucketModal.tsx @@ -55,7 +55,7 @@ export const EmptyBucketModal = ({ visible, bucket, onClose }: EmptyBucketModalP > - {`Confirm to delete all contents from ${bucket?.name}`} + {`Empty bucket “${bucket?.name}”`} -

Are you sure you want to empty the bucket "{bucket?.name}"?

+

+ Are you sure you want to remove all contents from the bucket “{bucket?.name}”? +

@@ -115,7 +122,9 @@ export const BucketTableRow = ({ -

+

{bucket.file_size_limit ? formatBytes(bucket.file_size_limit) : `Unset (${formattedGlobalUploadLimit})`} @@ -131,71 +140,10 @@ export const BucketTableRow = ({ -

- - +
+
) } - -type BucketDropdownMenuProps = { - bucket: Bucket - setSelectedBucket: (bucket: Bucket) => void - setModal: (modal: 'edit' | 'empty' | 'delete' | null) => void -} - -const BucketDropdownMenu = ({ bucket, setSelectedBucket, setModal }: BucketDropdownMenuProps) => { - return ( - - -
- + 0} /> {showSearchEmptyState ? ( @@ -50,8 +46,6 @@ const BucketsTableUnvirtualized = ({ projectRef={projectRef} formattedGlobalUploadLimit={formattedGlobalUploadLimit} getPolicyCount={getPolicyCount} - setSelectedBucket={setSelectedBucket} - setModal={setModal} /> )) )} @@ -65,15 +59,13 @@ const BucketsTableVirtualized = ({ projectRef, filterString, formattedGlobalUploadLimit, - setSelectedBucket, - setModal, getPolicyCount, }: BucketsTableProps) => { const showSearchEmptyState = buckets.length === 0 && filterString.length > 0 return ( 59} getItemKey={(bucket) => bucket.id}> - + 0} /> paddingColSpan={5} emptyContent={ @@ -90,8 +82,6 @@ const BucketsTableVirtualized = ({ projectRef={projectRef} formattedGlobalUploadLimit={formattedGlobalUploadLimit} getPolicyCount={getPolicyCount} - setSelectedBucket={setSelectedBucket} - setModal={setModal} /> )} diff --git a/apps/studio/components/interfaces/Storage/FilesBuckets/index.tsx b/apps/studio/components/interfaces/Storage/FilesBuckets/index.tsx index 41eda65243755..9444eb99a50c7 100644 --- a/apps/studio/components/interfaces/Storage/FilesBuckets/index.tsx +++ b/apps/studio/components/interfaces/Storage/FilesBuckets/index.tsx @@ -109,8 +109,6 @@ export const FilesBuckets = () => { projectRef={ref ?? '_'} filterString={filterString} formattedGlobalUploadLimit={formattedGlobalUploadLimit} - setSelectedBucket={setSelectedBucket} - setModal={setModal} getPolicyCount={getPolicyCount} /> diff --git a/apps/studio/components/interfaces/Storage/StorageMenuV2.tsx b/apps/studio/components/interfaces/Storage/StorageMenuV2.tsx index 3bd7d77596234..3103840f69ac6 100644 --- a/apps/studio/components/interfaces/Storage/StorageMenuV2.tsx +++ b/apps/studio/components/interfaces/Storage/StorageMenuV2.tsx @@ -34,7 +34,11 @@ export const StorageMenuV2 = () => {

{config.displayName}

- {isAlphaEnabled && ALPHA} + {isAlphaEnabled && ( + + New + + )}
diff --git a/apps/studio/components/interfaces/Storage/VectorBuckets/DeleteVectorBucketModal.tsx b/apps/studio/components/interfaces/Storage/VectorBuckets/DeleteVectorBucketModal.tsx index 40db9b068ea6a..a2946484160b9 100644 --- a/apps/studio/components/interfaces/Storage/VectorBuckets/DeleteVectorBucketModal.tsx +++ b/apps/studio/components/interfaces/Storage/VectorBuckets/DeleteVectorBucketModal.tsx @@ -61,7 +61,7 @@ export const DeleteVectorBucketModal = ({ visible={visible} size="medium" variant="destructive" - title={`Confirm deletion of ${bucketName}`} + title={`Delete bucket “${bucketName}”`} loading={isDeletingBucket || isDeletingIndexes} confirmPlaceholder="Type bucket name" confirmString={bucketName ?? ''} diff --git a/apps/studio/components/interfaces/Storage/VectorBuckets/index.tsx b/apps/studio/components/interfaces/Storage/VectorBuckets/index.tsx index 3bbb96f2b28f1..ba967a37e36a9 100644 --- a/apps/studio/components/interfaces/Storage/VectorBuckets/index.tsx +++ b/apps/studio/components/interfaces/Storage/VectorBuckets/index.tsx @@ -1,31 +1,19 @@ -import { ExternalLink, MoreVertical, Search, Trash2 } from 'lucide-react' -import Link from 'next/link' +import { ChevronRight, ExternalLink, Search } from 'lucide-react' +import { useRouter } from 'next/navigation' +import type React from 'react' import { useState } from 'react' import { useParams } from 'common' import { ScaffoldHeader, ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useVectorBucketsQuery } from 'data/storage/vector-buckets-query' -import { - Button, - Card, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from 'ui' +import { Bucket as BucketIcon } from 'icons' +import { Button, Card, cn, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui' import { Admonition } from 'ui-patterns' import { Input } from 'ui-patterns/DataInputs/Input' import { TimestampInfo } from 'ui-patterns/TimestampInfo' import { EmptyBucketState } from '../EmptyBucketState' import { CreateVectorBucketDialog } from './CreateVectorBucketDialog' -import { DeleteVectorBucketModal } from './DeleteVectorBucketModal' /** * [Joshen] Low-priority refactor: We should use a virtualized table here as per how we do it @@ -34,12 +22,9 @@ import { DeleteVectorBucketModal } from './DeleteVectorBucketModal' export const VectorsBuckets = () => { const { ref: projectRef } = useParams() + const router = useRouter() const [filterString, setFilterString] = useState('') - const [bucketForDeletion, setBucketForDeletion] = useState<{ - vectorBucketName: string - creationTime: string - } | null>(null) const { data, isLoading: isLoadingBuckets } = useVectorBucketsQuery({ projectRef }) const bucketsList = data?.vectorBuckets ?? [] @@ -51,13 +36,25 @@ export const VectorsBuckets = () => { bucket.vectorBucketName.toLowerCase().includes(filterString.toLowerCase()) ) + const handleBucketNavigation = ( + bucketName: string, + event: React.MouseEvent | React.KeyboardEvent + ) => { + const url = `/project/${projectRef}/storage/vectors/buckets/${encodeURIComponent(bucketName)}` + if (event.metaKey || event.ctrlKey) { + window.open(url, '_blank') + } else { + router.push(url) + } + } + return ( }> { // [Joshen] To update with Vector specific GH discussion href="https://github.com/orgs/supabase/discussions/40116" > - Leave feedback + Share feedback } > -

- Expect rapid changes, limited features, and possible breaking updates as we expand access. +

+ Vector buckets are now in private alpha. Expect rapid changes, limited features, and + possible breaking updates. Please share feedback as we refine the experience and expand + access.

-

Please share feedback as we refine the experience!

{!isLoadingBuckets && bucketsList.length === 0 ? ( @@ -103,14 +101,21 @@ export const VectorsBuckets = () => {
+ {filteredBuckets.length > 0 && ( + + Icon + + )} Name Created at - + + Actions + {filteredBuckets.length === 0 && filterString.length > 0 && ( - +

No results found

@@ -126,15 +131,18 @@ export const VectorsBuckets = () => { const created = +bucket.creationTime * 1000 return ( - + + + + - {name}

+

@@ -145,30 +153,8 @@ export const VectorsBuckets = () => {

-
- - - -
)} - - setBucketForDeletion(null)} - onSuccess={() => setBucketForDeletion(null)} - /> ) } diff --git a/apps/studio/components/layouts/PageLayout/PageHeader.tsx b/apps/studio/components/layouts/PageLayout/PageHeader.tsx index 98e3fef0f763a..d04c80c4c1d2a 100644 --- a/apps/studio/components/layouts/PageLayout/PageHeader.tsx +++ b/apps/studio/components/layouts/PageLayout/PageHeader.tsx @@ -48,10 +48,12 @@ export const PageHeader = ({ {(displayBreadcrumbs.length > 0 || (isCompact && (title || primaryActions || secondaryActions))) && (
-
+
{breadcrumbs.length > 0 ? ( - - + + {breadcrumbs.map((item, index) => ( @@ -81,19 +83,19 @@ export const PageHeader = ({ {isCompact && title && ( <> - - {title} + + {title} )} ) : isCompact ? ( - title +
{title}
) : null}
{isCompact && ( -
+
{secondaryActions && (
{secondaryActions}
)} diff --git a/apps/studio/pages/_app.tsx b/apps/studio/pages/_app.tsx index 854b9f596694e..4e1430b0d9960 100644 --- a/apps/studio/pages/_app.tsx +++ b/apps/studio/pages/_app.tsx @@ -2,6 +2,7 @@ import 'react-data-grid/lib/styles.css' import 'styles/code.scss' import 'styles/contextMenu.scss' import 'styles/editor.scss' +import 'styles/focus.scss' import 'styles/graphiql-base.scss' import 'styles/grid.scss' import 'styles/main.scss' diff --git a/apps/studio/pages/project/[ref]/storage/files/buckets/[bucketId].tsx b/apps/studio/pages/project/[ref]/storage/files/buckets/[bucketId].tsx index 433b8e73e2312..d7960d66f94df 100644 --- a/apps/studio/pages/project/[ref]/storage/files/buckets/[bucketId].tsx +++ b/apps/studio/pages/project/[ref]/storage/files/buckets/[bucketId].tsx @@ -1,9 +1,11 @@ -import { Edit, Shield } from 'lucide-react' +import { ChevronDown, FolderOpen, Settings, Shield, Trash2 } from 'lucide-react' import Link from 'next/link' import { useState } from 'react' import { useParams } from 'common' +import { DeleteBucketModal } from 'components/interfaces/Storage/DeleteBucketModal' import { EditBucketModal } from 'components/interfaces/Storage/EditBucketModal' +import { EmptyBucketModal } from 'components/interfaces/Storage/EmptyBucketModal' import StorageBucketsError from 'components/interfaces/Storage/StorageBucketsError' import { StorageExplorer } from 'components/interfaces/Storage/StorageExplorer/StorageExplorer' import { useSelectedBucket } from 'components/interfaces/Storage/StorageExplorer/useSelectedBucket' @@ -15,14 +17,22 @@ import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useStoragePolicyCounts } from 'hooks/storage/useStoragePolicyCounts' import { useStorageExplorerStateSnapshot } from 'state/storage-explorer' import type { NextPageWithLayout } from 'types' -import { Badge, Button } from 'ui' +import { + Badge, + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from 'ui' const BucketPage: NextPageWithLayout = () => { const { bucketId, ref } = useParams() const { data: project } = useSelectedProjectQuery() const { projectRef } = useStorageExplorerStateSnapshot() const { bucket, error, isSuccess, isError } = useSelectedBucket() - const [showEditModal, setShowEditModal] = useState(false) + const [modal, setModal] = useState<'edit' | 'empty' | 'delete' | null>(null) const { getPolicyCount } = useStoragePolicyCounts(bucket ? [bucket as Bucket] : []) const policyCount = bucket ? getPolicyCount(bucket.id) : 0 @@ -50,9 +60,13 @@ const BucketPage: NextPageWithLayout = () => { isCompact className="[&>div:first-child]:!border-b-0" // Override the border-b from ScaffoldContainer title={ -
- {bucket.name} - {bucket.public && Public} +
+ {bucket.name} + {bucket.public && ( + + Public + + )}
} breadcrumbs={[ @@ -77,9 +91,37 @@ const BucketPage: NextPageWithLayout = () => { > Policies - + + + + + + setModal('edit')} + > + +

Bucket settings

+
+ + setModal('empty')} + > + +

Empty bucket

+
+ setModal('delete')} + > + +

Delete bucket

+
+
+
} > @@ -88,11 +130,25 @@ const BucketPage: NextPageWithLayout = () => {
- setShowEditModal(false)} - /> + {bucket && ( + <> + setModal(null)} + /> + setModal(null)} + /> + setModal(null)} + /> + + )} ) } diff --git a/apps/studio/styles/focus.scss b/apps/studio/styles/focus.scss new file mode 100644 index 0000000000000..ceae2d560f553 --- /dev/null +++ b/apps/studio/styles/focus.scss @@ -0,0 +1,4 @@ +/* Focus styles for keyboard navigation on interactive table rows */ +.inset-focus { + @apply ease-out duration-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-brand-600 focus-visible:rounded-md transition-all; +} diff --git a/apps/ui-library/components/command-copy-button.tsx b/apps/ui-library/components/command-copy-button.tsx index 8aa85d40fc545..0b832fa054762 100644 --- a/apps/ui-library/components/command-copy-button.tsx +++ b/apps/ui-library/components/command-copy-button.tsx @@ -18,13 +18,14 @@ export function CommandCopyButton({ command }: { command: string }) { }, [copied]) const parseCommandForTelemetry = (cmd: string) => { - // Extract framework from URL (e.g., 'nextjs' from 'password-based-auth-nextjs.json') - const frameworkMatch = cmd.match(/ui\/r\/.*?-(nextjs|react|react-router|tanstack)\.json/) + // Extracts title and framework e.g. "title: password-based-auth, framework: nextjs" + const match = cmd.match( + /(?:\/ui\/r\/|@supabase\/)(.+)-(nextjs|react-router|react|tanstack|vue|nuxtjs)(?:@[^@\s]+|\.json)?$/ + ) - // if the block doesn't have a framework defined (like infinite query), default to react - const framework = frameworkMatch - ? (frameworkMatch[1] as 'nextjs' | 'react-router' | 'tanstack' | 'react') - : 'react' + const framework = + (match?.[2] as 'nextjs' | 'react-router' | 'tanstack' | 'react' | 'vue' | 'nuxtjs') ?? 'react' + const title = match?.[1] ?? '' // Extract package manager from command prefix (npx, pnpm, yarn, bun) const packageManager = cmd.startsWith('npx') @@ -37,15 +38,7 @@ export function CommandCopyButton({ command }: { command: string }) { ? ('bun' as const) : ('npm' as const) - // Extract template title from URL (e.g., 'password-based-auth' from 'password-based-auth-nextjs.json') - const titleMatch = cmd.match(/\/ui\/r\/(.*?)\.json/) - const title = (titleMatch ? titleMatch[1] : '').replaceAll(`-${framework}`, '') - - return { - framework, - packageManager, - title, - } + return { framework, packageManager, title } } return ( diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts index 03e4b5989021e..bdbd933725d6f 100644 --- a/packages/common/telemetry-constants.ts +++ b/packages/common/telemetry-constants.ts @@ -1219,7 +1219,7 @@ export interface SupabaseUiCommandCopyButtonClickedEvent { properties: { templateTitle: string command: string - framework: 'nextjs' | 'react-router' | 'tanstack' | 'react' + framework: 'nextjs' | 'react-router' | 'tanstack' | 'react' | 'vue' | 'nuxtjs' packageManager: 'npm' | 'pnpm' | 'yarn' | 'bun' } } diff --git a/packages/ui/src/components/shadcn/ui/badge.tsx b/packages/ui/src/components/shadcn/ui/badge.tsx index 0c32c9352db64..bce8621524cb9 100644 --- a/packages/ui/src/components/shadcn/ui/badge.tsx +++ b/packages/ui/src/components/shadcn/ui/badge.tsx @@ -3,34 +3,32 @@ import * as React from 'react' import { cn } from '../../../lib/utils/cn' -const badgeVariants = cva( - 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs bg-opacity-10 whitespace-nowrap', - { - variants: { - variant: { - default: 'bg-surface-200 text-foreground-light border border-strong', - warning: 'bg-warning text-warning border border-warning-500', - success: 'bg-brand text-brand-600 border border-brand-500', - destructive: 'bg-destructive text-destructive-600 border border-destructive-500', - brand: 'bg-brand text-brand-600 border border-brand-500', - secondary: - 'bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground', - outline: 'bg-transparent text border border-foreground-muted', - }, - size: { - small: 'px-2.5 py-0.5 text-xs', - large: 'px-3 py-0.5 rounded-full text-sm', - }, - dot: { - true: '-ml-0.5 mr-1.5 h-2 w-2 rounded-full', - }, +const badgeVariants = cva('inline-flex items-center rounded-full font-normal whitespace-nowrap', { + variants: { + variant: { + default: 'bg-surface-75 text-foreground-light border border-strong', + warning: 'bg-warning bg-opacity-10 text-warning border border-warning-500', + success: 'bg-brand bg-opacity-10 text-brand-600 border border-brand-500', + destructive: + 'bg-destructive bg-opacity-10 text-destructive-600 border border-destructive-500', + brand: 'bg-brand bg-opacity-10 text-brand-600 border border-brand-500', + secondary: + 'bg-secondary bg-opacity-10 hover:bg-secondary/80 border-transparent text-secondary-foreground', + outline: 'bg-transparent text border border-foreground-muted', }, - defaultVariants: { - variant: 'default', - size: 'small', + size: { + small: 'px-2 py-0.5 text-xs', + large: 'px-3 py-0.5 text-sm', }, - } -) + dot: { + true: '-ml-0.5 mr-1.5 h-2 w-2', + }, + }, + defaultVariants: { + variant: 'default', + size: 'small', + }, +}) export interface BadgeProps extends React.HTMLAttributes, @@ -39,7 +37,7 @@ export interface BadgeProps function Badge({ className, variant = 'default', - size, + size = 'small', dot = false, children, ...props diff --git a/packages/ui/src/components/shadcn/ui/dialog.tsx b/packages/ui/src/components/shadcn/ui/dialog.tsx index 98f625f482440..a1ebda78e551a 100644 --- a/packages/ui/src/components/shadcn/ui/dialog.tsx +++ b/packages/ui/src/components/shadcn/ui/dialog.tsx @@ -161,7 +161,8 @@ const DialogTitle = React.forwardRef< >(({ className, ...props }, ref) => ( ))