diff --git a/apps/design-system/content/docs/copywriting.mdx b/apps/design-system/content/docs/copywriting.mdx index b986af5d58a..4709faa48fe 100644 --- a/apps/design-system/content/docs/copywriting.mdx +++ b/apps/design-system/content/docs/copywriting.mdx @@ -127,7 +127,7 @@ Supabase UI copy is: ### Use title case for page titles and global navigation -Use title case for page names in the main nav and document titles (e.g. "Database Settings", "Project Settings"). This includes browser tab titles set via the `browserTitle` prop on layout components (e.g. "New Pipeline", not "New pipeline"). +Use title case for page names in the main nav and document titles (e.g. "Database Settings", "Project Settings"). This distinguishes the page as a destination from section labels and in-page headings, which use sentence case. ### Use declarative page descriptions diff --git a/apps/design-system/registry/default/example/form-patterns-pagelayout.tsx b/apps/design-system/registry/default/example/form-patterns-pagelayout.tsx index 270c6320bd5..42deb0f33d0 100644 --- a/apps/design-system/registry/default/example/form-patterns-pagelayout.tsx +++ b/apps/design-system/registry/default/example/form-patterns-pagelayout.tsx @@ -252,7 +252,7 @@ export default function FormPatternsPageLayout() { - milliseconds + ms diff --git a/apps/design-system/registry/default/example/form-patterns-sidepanel.tsx b/apps/design-system/registry/default/example/form-patterns-sidepanel.tsx index e8e6af1d1a9..1b3a146e357 100644 --- a/apps/design-system/registry/default/example/form-patterns-sidepanel.tsx +++ b/apps/design-system/registry/default/example/form-patterns-sidepanel.tsx @@ -265,7 +265,7 @@ export default function FormPatternsSidePanel() { - milliseconds + ms diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationFormFieldCopy.ts b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationFormFieldCopy.ts index 220541d9270..fd69f9b8d5c 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationFormFieldCopy.ts +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationFormFieldCopy.ts @@ -40,12 +40,12 @@ export const getDestinationTypeCreateDescription = (type: DestinationType) => { export const BIGQUERY_PROJECT_ID_FIELD_COPY = { label: 'Project ID', - description: 'Google Cloud project ID where data will be sent.', + description: 'The Google Cloud project ID where data will be sent', } as const export const BIGQUERY_DATASET_ID_FIELD_COPY = { label: 'Dataset ID', - description: 'BigQuery dataset where replicated tables will be created.', + description: 'The BigQuery dataset where replicated tables will be created', } as const export const ANALYTICS_BUCKET_BUCKET_FIELD_COPY = { diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PublicationSelect.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PublicationSelect.tsx deleted file mode 100644 index a55089f0d58..00000000000 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PublicationSelect.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import { useParams } from 'common' -import { Check, Loader2, Plus } from 'lucide-react' -import { useId, useState } from 'react' -import { ControllerRenderProps } from 'react-hook-form' -import { - cn, - ComboboxTrigger, - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, - CommandSeparator, - Popover, - PopoverContent, - PopoverTrigger, - ScrollArea, -} from 'ui' -import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' - -import type { DestinationPanelSchemaType } from './DestinationForm.schema' -import { useReplicationPublicationsQuery } from '@/data/replication/publications-query' - -interface PublicationSelectProps { - sourceId?: number - field: ControllerRenderProps - onNewPublicationClick: () => void -} - -export const PublicationSelect = ({ - sourceId, - field, - onNewPublicationClick, -}: PublicationSelectProps) => { - const { ref: projectRef } = useParams() - const listboxId = useId() - const [open, setOpen] = useState(false) - const selectedPublication = field.value ?? '' - - const { - data: publications = [], - isPending, - isFetching, - refetch: refetchPublications, - } = useReplicationPublicationsQuery({ projectRef, sourceId }) - const isLoadingPublications = isPending || isFetching - const showLoadingState = isLoadingPublications && publications.length === 0 - - const handlePublicationSelect = (publicationName: string) => { - setOpen(false) - field.onChange(publicationName) - } - - return ( - { - setOpen(nextOpen) - if (nextOpen) { - if (typeof projectRef !== 'undefined' && typeof sourceId !== 'undefined') { - refetchPublications() - } - } - - if (!nextOpen) { - field.onBlur() - } - }} - > - - - ) : undefined - } - > - {showLoadingState - ? 'Loading publications...' - : selectedPublication || 'Select publication'} - - - - - - - No publications found - - {showLoadingState ? ( -
- -
- ) : ( - <> - {publications.length === 0 ? ( -
-

No publications available

-

Publications with no tables are hidden

-
- ) : ( - - 7 ? 'h-[210px]' : ''} - onWheel={(event) => event.stopPropagation()} - > - {publications.map((pub) => ( - { - handlePublicationSelect(pub.name) - }} - > -
-
-

{pub.name}

-

- {pub.tables.length} {pub.tables.length === 1 ? 'table' : 'tables'} -

-
- -
-
- ))} -
-
- )} - - - - - { - setOpen(false) - onNewPublicationClick() - }} - > - - New publication - - - - )} -
-
-
-
- ) -} diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PublicationSelection.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PublicationSelection.tsx index 9eaebb92380..e0ac6818d71 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PublicationSelection.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PublicationSelection.tsx @@ -6,8 +6,7 @@ import { Admonition } from 'ui-patterns/Admonition' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import type { DestinationPanelSchemaType } from './DestinationForm.schema' -import { PUBLICATION_FIELD_COPY } from './DestinationFormFieldCopy' -import { PublicationSelect } from './PublicationSelect' +import { PublicationsComboBox } from './PublicationsComboBox' import { useReplicationPublicationsQuery } from '@/data/replication/publications-query' import { useReplicationSourceId } from '@/data/replication/sources-query' @@ -41,11 +40,11 @@ export const PublicationSelection = ({ render={({ field }) => ( - { diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PublicationsComboBox.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PublicationsComboBox.tsx new file mode 100644 index 00000000000..2e6e8f70144 --- /dev/null +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PublicationsComboBox.tsx @@ -0,0 +1,167 @@ +import { useParams } from 'common' +import { Check, ChevronsUpDown, Loader2, Plus } from 'lucide-react' +import { useEffect, useState } from 'react' +import { ControllerRenderProps } from 'react-hook-form' +import { + Badge, + Button, + cn, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + Popover, + PopoverContent, + PopoverTrigger, + ScrollArea, +} from 'ui' +import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' + +import type { DestinationPanelSchemaType } from './DestinationForm.schema' +import { useReplicationPublicationsQuery } from '@/data/replication/publications-query' + +interface PublicationsComboBoxProps { + sourceId?: number + field: ControllerRenderProps + onNewPublicationClick: () => void +} + +export const PublicationsComboBox = ({ + sourceId, + field, + onNewPublicationClick, +}: PublicationsComboBoxProps) => { + const { ref: projectRef } = useParams() + + const [searchTerm, setSearchTerm] = useState('') + const [dropdownOpen, setDropdownOpen] = useState(false) + const [selectedPublication, setSelectedPublication] = useState(field?.value || '') + + const { + data: publications = [], + isPending, + isFetching, + refetch: refetchPublications, + } = useReplicationPublicationsQuery({ projectRef, sourceId }) + const isLoadingPublications = isPending || isFetching + const showLoadingState = isLoadingPublications && publications.length === 0 + + function handlePublicationSelect(pub: string) { + setSelectedPublication(pub) + setDropdownOpen(false) + field.onChange(pub) + } + + useEffect(() => { + setSelectedPublication(field?.value || '') + }, [field?.value]) + + return ( + { + setDropdownOpen(open) + if (open) { + if (typeof projectRef !== 'undefined' && typeof sourceId !== 'undefined') { + refetchPublications() + } + } + + if (!open && field?.onBlur) { + field.onBlur() + } + }} + > + + + + + + + + No publications found + + {showLoadingState && ( +
+ +
+ )} + + + {publications.length === 0 && !showLoadingState && ( +
+

No publications available

+

Publications with no tables are hidden

+
+ )} + 7 ? 'h-[210px]' : ''} + onWheel={(e) => e.stopPropagation()} + > + {publications.map((pub) => ( + { + handlePublicationSelect(pub.name) + }} + onClick={() => { + handlePublicationSelect(pub.name) + }} + > + {pub.name} +
+ + {pub.tables.length} {pub.tables.length === 1 ? 'table' : 'tables'} + + {selectedPublication === pub.name && ( + + )} +
+
+ ))} +
+
+ + + + + + +

New publication

+
+
+
+
+
+
+ ) +}