diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx index e8b266d531..c0e57e7bc6 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx @@ -1,5 +1,11 @@ import { sortBy } from 'lodash' -import MultiSelect from 'ui-patterns/MultiSelectDeprecated' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { SYSTEM_ROLES } from '@/components/interfaces/Database/Roles/Roles.constants' @@ -51,13 +57,28 @@ export const PolicyRoles = ({ selectedRoles, onUpdateSelectedRoles }: PolicyRole {isLoading && } {isError && } {isSuccess && ( - + + + + + {formattedRoles.map((role) => ( + + {role.name} + + ))} + + + )} diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx index d132e7a304..1de3817ad9 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx @@ -29,7 +29,13 @@ import { SelectItem_Shadcn_, SelectTrigger_Shadcn_, } from 'ui' -import { MultiSelectV2 } from 'ui-patterns/MultiSelectDeprecated/MultiSelectV2' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' import { useDatabaseRolesQuery } from '@/data/database-roles/database-roles-query' import { useTablesQuery } from '@/data/tables/tables-query' @@ -298,18 +304,43 @@ export const PolicyDetailsV2 = ({ name="roles" render={({ field }) => ( - + Target Roles to clause - field.onChange(roles.join(', '))} disabled={!canUpdatePolicies} - options={formattedRoles} - value={field.value.length === 0 ? [] : field.value?.split(', ')} - placeholder="Defaults to all (public) roles if none selected" - searchPlaceholder="Search for a role" - onChange={(roles) => form.setValue('roles', roles.join(', '))} - /> + values={field.value.length === 0 ? [] : field.value?.split(', ')} + size="small" + > + + + + {formattedRoles.map((role) => ( + + {role.name} + + ))} + + + diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx index 91a78d00b3..7972413c50 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx @@ -79,14 +79,14 @@ export const PolicyRow = ({ - {policy.command} + {policy.command}
{displayedRoles.slice(0, 2).map((role, i) => ( - {role} + {role} {i < Math.min(displayedRoles.length, 2) - 1 ? ', ' : ' '} ))} @@ -95,13 +95,24 @@ export const PolicyRow = ({
- + + {displayedRoles.length - 2} more - +
- - {displayedRoles.join(', ')} + + {displayedRoles.slice(2).map((role, i, arr) => ( + <> + + {role} + + {i < arr.length - 1 && ', '} + + ))}
)} diff --git a/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx b/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx index 5236fe5501..40d6174a1b 100644 --- a/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx +++ b/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx @@ -24,8 +24,13 @@ import { } from 'ui' import { Admonition } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' -import { MultiSelectOption } from 'ui-patterns/MultiSelectDeprecated' -import { MultiSelectV2 } from 'ui-patterns/MultiSelectDeprecated/MultiSelectV2' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { INDEX_TYPES } from './Indexes.constants' @@ -101,7 +106,7 @@ export const CreateIndexSidePanel = ({ visible, onClose }: CreateIndexSidePanelP } const columns = tableColumns?.[0]?.columns ?? [] - const columnOptions: MultiSelectOption[] = columns + const columnOptions = columns .filter((column): column is NonNullable => column !== null) .map((column) => ({ id: column.attname, @@ -328,16 +333,36 @@ CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexTyp {selectedEntity && ( - + {isLoadingTableColumns && } {isSuccessTableColumns && ( - + + + + + {columnOptions.map((option) => ( + + {option.name} + + ))} + + + )} )} diff --git a/apps/studio/components/interfaces/Database/Indexes/Indexes.tsx b/apps/studio/components/interfaces/Database/Indexes/Indexes.tsx index 266b3f879c..85160122e0 100644 --- a/apps/studio/components/interfaces/Database/Indexes/Indexes.tsx +++ b/apps/studio/components/interfaces/Database/Indexes/Indexes.tsx @@ -31,7 +31,7 @@ import { useQuerySchemaState } from '@/hooks/misc/useSchemaQueryState' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { useIsProtectedSchema } from '@/hooks/useProtectedSchemas' -const Indexes = () => { +export const Indexes = () => { const { data: project } = useSelectedProjectQuery() const { schema: urlSchema, table } = useParams() @@ -278,7 +278,8 @@ const Indexes = () => { visible={!!selectedIndexToDelete} title={ <> - Confirm to delete index {selectedIndexToDelete?.name} + Confirm to delete index{' '} + {selectedIndexToDelete?.name} } confirmLabel="Confirm delete" @@ -292,6 +293,7 @@ const Indexes = () => { description: 'Deleting an index that is still in use will cause queries to slow down, and in some cases causing significant performance issues.', }} + className="pt-0" >
  • @@ -311,5 +313,3 @@ const Indexes = () => { ) } - -export default Indexes diff --git a/apps/studio/components/interfaces/Support/AffectedServicesSelector.tsx b/apps/studio/components/interfaces/Support/AffectedServicesSelector.tsx index 7ca04998d9..43879bb4fe 100644 --- a/apps/studio/components/interfaces/Support/AffectedServicesSelector.tsx +++ b/apps/studio/components/interfaces/Support/AffectedServicesSelector.tsx @@ -4,7 +4,13 @@ import { SupportCategories } from '@supabase/shared-types/out/constants' import type { UseFormReturn } from 'react-hook-form' import { FormControl, FormField } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' -import { MultiSelectV2 } from 'ui-patterns/MultiSelectDeprecated/MultiSelectV2' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' import { SERVICE_OPTIONS, type ExtendedSupportCategories } from './Support.constants' import type { SupportFormValues } from './SupportForm.schema' @@ -29,13 +35,31 @@ export function AffectedServicesSelector({ form, category }: AffectedServicesSel render={({ field }) => ( - form.setValue('affectedServices', services.join(', '))} - /> + field.onChange(services.join(', '))} + > + + + + {SERVICE_OPTIONS.map((service) => ( + + {service.name} + + ))} + + + )} diff --git a/apps/studio/pages/project/[ref]/database/indexes.tsx b/apps/studio/pages/project/[ref]/database/indexes.tsx index 858b99dfcd..f9f558a88d 100644 --- a/apps/studio/pages/project/[ref]/database/indexes.tsx +++ b/apps/studio/pages/project/[ref]/database/indexes.tsx @@ -11,9 +11,9 @@ import { } from 'ui-patterns/PageHeader' import { PageSection, PageSectionContent } from 'ui-patterns/PageSection' -import Indexes from '@/components/interfaces/Database/Indexes/Indexes' +import { Indexes } from '@/components/interfaces/Database/Indexes/Indexes' import DatabaseLayout from '@/components/layouts/DatabaseLayout/DatabaseLayout' -import DefaultLayout from '@/components/layouts/DefaultLayout' +import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { DocsButton } from '@/components/ui/DocsButton' import { DOCS_URL } from '@/lib/constants' import type { NextPageWithLayout } from '@/types' diff --git a/e2e/studio/features/database.spec.ts b/e2e/studio/features/database.spec.ts index 7cd1d8a1f5..80cc3a6520 100644 --- a/e2e/studio/features/database.spec.ts +++ b/e2e/studio/features/database.spec.ts @@ -651,16 +651,26 @@ test.describe('Database', () => { await dropTable(databaseTableName) } ) + const indexWait = waitForApiResponse(page, 'pg-meta', ref, 'query?key=indexes-public') await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/indexes?schema=public`)) // Wait for database indexes to be populated - await waitForApiResponse(page, 'pg-meta', ref, 'query?key=indexes-public') + await indexWait // create new index await page.getByRole('button', { name: 'Create index' }).click() await page.getByRole('button', { name: 'Choose a table' }).click() + + const columnsWait = waitForApiResponse( + page, + 'pg-meta', + ref, + `query?key=table-columns-public-${databaseTableName}` + ) await page.getByRole('option', { name: databaseTableName, exact: true }).click() - await page.getByText('Choose which columns to create an index on').click() + await columnsWait + + await page.getByRole('combobox', { name: 'Select up to 32 columns' }).click() await page.getByRole('option', { name: databaseColumnName }).click() await page.getByRole('button', { name: 'Create index' }).click() await expect( @@ -1136,9 +1146,7 @@ test.describe('Database Enumerated Types', () => { await quotedEnumCreateWait const quotedEnumRow = page.getByRole('row', { name: `${quotedEnumName}` }) await expect(quotedEnumRow).toContainText(quotedEnumName) - await expect(quotedEnumRow).toContainText( - `${quotedEnumValue1Name}, ${quotedEnumValue2Name}` - ) + await expect(quotedEnumRow).toContainText(`${quotedEnumValue1Name}, ${quotedEnumValue2Name}`) await quotedEnumRow.getByRole('button').click() await page.getByRole('menuitem', { name: 'Update type' }).click() diff --git a/e2e/studio/features/rls-policies.spec.ts b/e2e/studio/features/rls-policies.spec.ts index ac615c4a38..b6a3cabd17 100644 --- a/e2e/studio/features/rls-policies.spec.ts +++ b/e2e/studio/features/rls-policies.spec.ts @@ -1,9 +1,9 @@ import { expect, Page } from '@playwright/test' +import { createTableWithRLS, dropTable } from '../utils/db/queries.js' import { test, withSetupCleanup } from '../utils/test.js' import { toUrl } from '../utils/to-url.js' import { createApiResponseWaiter, waitForApiResponse } from '../utils/wait-for-response.js' -import { createTableWithRLS, dropTable } from '../utils/db/queries.js' /** * Helper function to navigate to policies page and wait for it to load @@ -283,7 +283,7 @@ test.describe('RLS Policies', () => { await page.getByRole('radio', { name: 'INSERT' }).click() // Select target role - authenticated - await page.getByText('Defaults to all (public) roles if none selected').click() + await page.getByRole('combobox', { name: 'Target Roles' }).click() await page.getByRole('option', { name: 'authenticated' }).click() // Close the dropdown @@ -338,7 +338,7 @@ test.describe('RLS Policies', () => { await page.getByRole('radio', { name: 'UPDATE' }).click() // Select authenticated role - await page.getByText('Defaults to all (public) roles if none selected').click() + await page.getByRole('combobox', { name: 'Target Roles' }).click() await page.getByRole('option', { name: 'authenticated' }).click() await page.keyboard.press('Escape') @@ -390,7 +390,7 @@ test.describe('RLS Policies', () => { await page.getByRole('radio', { name: 'DELETE' }).click() // Select authenticated role - await page.getByText('Defaults to all (public) roles if none selected').click() + await page.getByRole('combobox', { name: 'Target Roles' }).click() await page.getByRole('option', { name: 'authenticated' }).click() await page.keyboard.press('Escape') diff --git a/packages/ui-patterns/src/MultiSelectDeprecated/Badges.tsx b/packages/ui-patterns/src/MultiSelectDeprecated/Badges.tsx deleted file mode 100644 index e3e8200208..0000000000 --- a/packages/ui-patterns/src/MultiSelectDeprecated/Badges.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { X } from 'lucide-react' - -/** - * @deprecated Use ./multi-select instead - */ -export const BadgeDisabled = ({ name }: { name: string }) => ( -
    - {name} -
    -) - -/** - * @deprecated Use ./multi-select instead - */ -export const BadgeSelected = ({ - name, - handleRemove, -}: { - name: string - handleRemove: () => void -}) => ( -
    e.preventDefault()} - > - {name} - { - e.preventDefault() - e.stopPropagation() - handleRemove() - }} - /> -
    -) diff --git a/packages/ui-patterns/src/MultiSelectDeprecated/MultiSelectV2.tsx b/packages/ui-patterns/src/MultiSelectDeprecated/MultiSelectV2.tsx deleted file mode 100644 index 799cc9b8e9..0000000000 --- a/packages/ui-patterns/src/MultiSelectDeprecated/MultiSelectV2.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { orderBy, without } from 'lodash' -import { Check, ChevronDown } from 'lucide-react' -import { ReactNode, useState } from 'react' -import { - cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - Popover_Shadcn_, - PopoverContent_Shadcn_, - PopoverTrigger_Shadcn_, - ScrollArea, -} from 'ui' - -import { BadgeDisabled, BadgeSelected } from './Badges' - -interface MultiSelectOption { - id: string | number - value: string - name: string - description?: string - disabled: boolean -} - -interface MultiSelectProps { - value: string[] - options: MultiSelectOption[] - placeholder?: string | ReactNode - searchPlaceholder?: string - disabled?: boolean - onChange?(x: string[]): void -} - -/** - * @deprecated Use ./multi-select instead - */ -export const MultiSelectV2 = ({ - options, - value, - placeholder, - searchPlaceholder = 'Search for option', - disabled = false, - onChange = () => {}, -}: MultiSelectProps) => { - const [open, setOpen] = useState(false) - const [selected, setSelected] = useState(value || []) - - // Selected is `value` if defined, otherwise use local useState - const selectedOptions = value || selected - - // Order the options so disabled items are at the beginning - const formattedOptions = orderBy(options, ['disabled'], ['desc']) - - const checkIfActive = (option: MultiSelectOption) => { - const isOptionSelected = (selectedOptions || []).find((x) => x === option.value) - return isOptionSelected !== undefined - } - - const handleChange = (option: MultiSelectOption) => { - const _selected = selectedOptions - const isActive = checkIfActive(option) - - const updatedPayload = isActive - ? [...without(_selected, option.value)] - : [..._selected.concat([option.value])] - - // Payload must always include disabled options - const compulsoryOptions = options - .filter((option) => option.disabled) - .map((option) => option.name) - - const formattedPayload = [...new Set(updatedPayload.concat(compulsoryOptions))] - - setSelected(formattedPayload) - onChange(formattedPayload) - } - - return ( -
    - - -
    setOpen(true)} - > - {selectedOptions.length === 0 && placeholder && ( -
    - {placeholder} -
    - )} - {selectedOptions.map((value, idx) => { - const id = `${value}-${idx}` - const option = formattedOptions.find((x) => x.value === value) - const isDisabled = option?.disabled ?? false - if (!option) { - return <> - } else if (isDisabled) { - return - } else { - return ( - handleChange(option)} - /> - ) - } - })} -
    - -
    -
    -
    - - - - event.stopPropagation()}> - No options found - - 7 ? 'h-[210px]' : '')}> - {formattedOptions?.map((option) => { - const active = - selectedOptions && - selectedOptions.find((selected) => { - return selected === option.value - }) - ? true - : false - return ( - handleChange(option)} - onSelect={() => handleChange(option)} - > -
    - {option.name} - {active && } -
    -
    - ) - })} -
    -
    -
    -
    -
    -
    -
    - ) -} diff --git a/packages/ui-patterns/src/MultiSelectDeprecated/index.tsx b/packages/ui-patterns/src/MultiSelectDeprecated/index.tsx deleted file mode 100644 index c4cca1e2d5..0000000000 --- a/packages/ui-patterns/src/MultiSelectDeprecated/index.tsx +++ /dev/null @@ -1,274 +0,0 @@ -import clsx from 'clsx' -import { filter, orderBy, without } from 'lodash' -import { AlertCircle, Check, ChevronDown, Plus, Search } from 'lucide-react' -import { ReactNode, useEffect, useRef, useState } from 'react' -import { - Input, - Popover_Shadcn_, - PopoverContent_Shadcn_, - PopoverTrigger_Shadcn_, - ScrollArea, -} from 'ui' - -import { BadgeDisabled, BadgeSelected } from './Badges' - -export interface MultiSelectOption { - id: string | number - value: string - name: string - description?: string - disabled: boolean -} - -interface Props { - value: string[] - options: MultiSelectOption[] - label?: string - error?: string - placeholder?: string | ReactNode - searchPlaceholder?: string - descriptionText?: string | ReactNode - emptyMessage?: string | ReactNode - disabled?: boolean - allowDuplicateSelection?: boolean - onChange?(x: string[]): void -} - -/** - * Copy styling from supabase/ui default.theme - * input base + standard - */ - -/** - * @deprecated Use ./multi-select instead - */ -export default function MultiSelect({ - options, - value, - label, - error, - descriptionText, - placeholder, - searchPlaceholder = 'Search for option', - emptyMessage, - disabled, - allowDuplicateSelection = false, - onChange = () => {}, -}: Props) { - const ref = useRef(null) - - const [open, setOpen] = useState(false) - const [selected, setSelected] = useState(value || []) - const [searchString, setSearchString] = useState('') - const [inputWidth, setInputWidth] = useState(128) - - // Selected is `value` if defined, otherwise use local useState - const selectedOptions = value || selected - - // Calculate width of the Popover - useEffect(() => { - setInputWidth(ref.current ? (ref.current as any).offsetWidth : inputWidth) - }, [ref.current]) - - useEffect(() => { - if (!open) setSearchString('') - }, [open]) - - const width = `${inputWidth}px` - - // Order the options so disabled items are at the beginning - const formattedOptions = orderBy(options, ['disabled'], ['desc']) - - // Options to show in Popover menu - const filteredOptions = - searchString.length > 0 - ? filter(formattedOptions, (option) => !option.disabled && option.name.includes(searchString)) - : filter(formattedOptions, { disabled: false }) - - const checkIfActive = (option: MultiSelectOption) => { - const isOptionSelected = (selectedOptions || []).find((x) => x === option.value) - return isOptionSelected !== undefined - } - - const handleRemove = (idx: number) => { - const updatedSelected = selected.filter((_x, index) => index !== idx) - setSelected(updatedSelected) - onChange(updatedSelected) - } - - const handleChange = (option: MultiSelectOption) => { - const _selected = selectedOptions - const isActive = checkIfActive(option) - - const updatedPayload = allowDuplicateSelection - ? [..._selected.concat([option.value])] - : isActive - ? [...without(_selected, option.value)] - : [..._selected.concat([option.value])] - - // Payload must always include disabled options - const compulsoryOptions = options - .filter((option) => option.disabled) - .map((option) => option.name) - - const formattedPayload = allowDuplicateSelection - ? updatedPayload.concat(compulsoryOptions) - : [...new Set(updatedPayload.concat(compulsoryOptions))] - - setSelected(formattedPayload) - onChange(formattedPayload) - } - - return ( -
    - {label && } -
    - - -
    setOpen(true)} - > - {selectedOptions.length === 0 && placeholder && ( -
    - {placeholder} -
    - )} - {selectedOptions.map((value, idx) => { - const id = `${value}-${idx}` - const option = formattedOptions.find((x) => x.value === value) - const isDisabled = option?.disabled ?? false - if (!option) { - return <> - } else if (isDisabled) { - return - } else { - return ( - - allowDuplicateSelection ? handleRemove(idx) : handleChange(option) - } - /> - ) - } - })} -
    - -
    -
    -
    - - } - placeholder={searchPlaceholder} - value={searchString} - onChange={(e) => setSearchString(e.target.value)} - /> - 5 ? 'h-[225px]' : '')}> - {filteredOptions.length >= 1 ? ( - filteredOptions.map((option) => { - const active = - !allowDuplicateSelection && - selectedOptions && - selectedOptions.find((selected) => { - return selected === option.value - }) - ? true - : false - - return ( -
    handleChange(option)} - className={[ - 'text-typography-body-light [[data-theme*=dark]_&]:text-typography-body-dark', - 'group flex cursor-pointer items-center justify-between transition', - 'space-x-1 rounded bg-transparent p-2 px-4 text-sm hover:bg-overlay-hover', - `${active ? ' [[data-theme*=dark]_&]:bg-green-600/25' : ''}`, - ].join(' ')} - > -
    -

    {option.name}

    - {option.description !== undefined && ( -

    {option.description}

    - )} -
    - {active && ( - - )} - {allowDuplicateSelection && ( -
    - -

    Add value

    -
    - )} -
    - ) - }) - ) : options.length === 0 ? ( -
    - {emptyMessage ? ( - emptyMessage - ) : ( -
    - -

    No options available

    -
    - )} -
    - ) : ( -
    - {emptyMessage ? ( - emptyMessage - ) : ( -
    -

    No options found

    -
    - )} -
    - )} -
    -
    -
    -
    - - {descriptionText && ( - {descriptionText} - )} - {error && {error}} -
    - ) -} diff --git a/packages/ui-patterns/src/multi-select/multi-select.tsx b/packages/ui-patterns/src/multi-select/multi-select.tsx index f1190b62d4..1039675ca9 100644 --- a/packages/ui-patterns/src/multi-select/multi-select.tsx +++ b/packages/ui-patterns/src/multi-select/multi-select.tsx @@ -2,8 +2,17 @@ import { cva, VariantProps } from 'class-variance-authority' import { Check, ChevronsUpDown, X as RemoveIcon } from 'lucide-react' +// @ts-ignore Required to avoid TS error: The inferred type of MultiSelectorContent cannot be named without a reference to @radix-ui +import type { Popover as PopoverPrimitive } from 'radix-ui' import React, { useEffect } from 'react' -import { Badge, cn, Popover_Shadcn_ as Popover, PopoverAnchor_Shadcn_ as PopoverAnchor } from 'ui' +import { + Badge, + cn, + Popover_Shadcn_ as Popover, + PopoverAnchor_Shadcn_ as PopoverAnchor, + PopoverContent_Shadcn_ as PopoverContent, + PopoverContentProps_Shadcn_ as PopoverContentProps, +} from 'ui' import { Command, CommandEmpty, @@ -11,7 +20,6 @@ import { CommandItem, CommandList, } from 'ui/src/components/shadcn/ui/command' -import { PopoverContent } from 'ui/src/components/shadcn/ui/popover' import { SIZE_VARIANTS, SIZE_VARIANTS_DEFAULT } from 'ui/src/lib/constants' interface MultiSelectContextProps { @@ -286,7 +294,7 @@ const MultiSelectorTrigger = React.forwardRef>( - ({ className, children }, ref) => { +const MultiSelectorContent = React.forwardRef( + ({ className, children, ...props }, ref) => { const { id } = useMultiSelect() return ( {children} diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index 1e2c20d775..a5a207e764 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -133,6 +133,7 @@ export { PopoverContent as PopoverContent_Shadcn_, PopoverAnchor as PopoverAnchor_Shadcn_, PopoverSeparator as PopoverSeparator_Shadcn_, + type PopoverContentProps as PopoverContentProps_Shadcn_, } from './src/components/shadcn/ui/popover' export { diff --git a/packages/ui/src/components/shadcn/ui/popover.tsx b/packages/ui/src/components/shadcn/ui/popover.tsx index 74702c74c4..2e93ef7685 100644 --- a/packages/ui/src/components/shadcn/ui/popover.tsx +++ b/packages/ui/src/components/shadcn/ui/popover.tsx @@ -10,7 +10,7 @@ const Popover = PopoverPrimitive.Root const PopoverTrigger = PopoverPrimitive.Trigger const PopoverAnchor = PopoverPrimitive.Anchor -type PopoverContentProps = { +export type PopoverContentProps = { align?: 'center' | 'start' | 'end' sideOffset?: number sameWidthAsTrigger?: boolean