diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PaymentMethodSelection.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PaymentMethodSelection.tsx index 1322170ed05..8066f002fc6 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PaymentMethodSelection.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PaymentMethodSelection.tsx @@ -14,7 +14,15 @@ import { useState, } from 'react' import { toast } from 'sonner' -import { Checkbox, Listbox } from 'ui' +import { + Checkbox, + Select_Shadcn_, + SelectContent_Shadcn_, + SelectItem_Shadcn_, + SelectTrigger_Shadcn_, + SelectValue_Shadcn_, +} from 'ui' +import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { getStripeElementsAppearanceOptions } from '@/components/interfaces/Billing/Payment/Payment.utils' @@ -289,48 +297,56 @@ const PaymentMethodSelection = forwardRef(function PaymentMethodSelection(

Retrieving payment methods

) : paymentMethods?.data && paymentMethods?.data.length > 0 && !setupNewPaymentMethod ? ( - - {paymentMethods?.data.map((method: any) => { - const label = `•••• •••• •••• ${method.card.last4}` - return ( - { - return ( - Credit Card Brand - ) - }} - > -
{label}
-
- ) - })} -
{ - setSetupNewPaymentMethod(true) + { + if (value === 'new') { + setSetupNewPaymentMethod(true) + return + } + onSelectPaymentMethod(value) }} > - -

- Add new payment method -

-
-
+ + + + + {paymentMethods?.data.map((method) => { + const label = `•••• •••• •••• ${method.card?.last4}` + return ( + +
+ Credit Card Brand + {label} +
+
+ ) + })} + +
+ +

+ Add new payment method +

+
+
+
+ + ) : null} {stripePromise && setupIntent && customerProfile && ( diff --git a/apps/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx b/apps/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx index 05d2f99e85b..8332ce063de 100644 --- a/apps/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx +++ b/apps/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx @@ -1,11 +1,23 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQueryClient } from '@tanstack/react-query' import { useFlag } from 'common' -import { Loader, Shield, Users, Wrench } from 'lucide-react' +import { Loader, Shield, Wrench } from 'lucide-react' import { useEffect, useState } from 'react' import { toast } from 'sonner' -import { Button, InfoIcon, Listbox, Loading, Modal, WarningIcon } from 'ui' +import { + Button, + InfoIcon, + Loading, + Modal, + Select_Shadcn_, + SelectContent_Shadcn_, + SelectItem_Shadcn_, + SelectTrigger_Shadcn_, + SelectValue_Shadcn_, + WarningIcon, +} from 'ui' import { Admonition } from 'ui-patterns' +import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { DocsButton } from '@/components/ui/DocsButton' @@ -28,7 +40,7 @@ export const TransferProjectButton = () => { const organizations = (allOrganizations || []).filter((it) => it.id !== projectOrgId) - const [selectedOrg, setSelectedOrg] = useState() + const [selectedOrg, setSelectedOrg] = useState() const { mutate: transferProject, @@ -185,27 +197,30 @@ export const TransferProjectButton = () => { You do not have any organizations you can transfer your project to. ) : ( - setSelectedOrg(slug)} - placeholder="Select Organization" + label="Select Target Organization" + className="gap-[2px]" + size="tiny" > - - Select Organization - - {organizations.map((x: any) => ( - } - > - {x.name} - - ))} - + setSelectedOrg(slug)} + value={selectedOrg} + > + + + + + {organizations.map((x) => ( + + {x.name} + + ))} + + + )} )} diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx index ada532a66bc..8840bbb0b7a 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx @@ -1,16 +1,21 @@ import { FOREIGN_KEY_CASCADE_ACTION } from '@supabase/pg-meta' import type { PostgresTable } from '@supabase/postgres-meta' import { sortBy } from 'lodash' -import { ArrowRight, Database, HelpCircle, Loader2, Table, X } from 'lucide-react' +import { ArrowRight, HelpCircle, Loader2, X } from 'lucide-react' import { Fragment, useEffect, useState } from 'react' import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, Button, - Listbox, + Select_Shadcn_, + SelectContent_Shadcn_, + SelectItem_Shadcn_, + SelectTrigger_Shadcn_, + SelectValue_Shadcn_, SidePanel, } from 'ui' +import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { ActionBar } from '../ActionBar' import { NUMERICAL_TYPES, TEXT_TYPES } from '../SidePanelEditor.constants' @@ -24,7 +29,6 @@ import { type ForeignKeyDirtyState, } from './ForeignKeySelector.utils' import { DiscardChangesConfirmationDialog } from '@/components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog' -import { DocsButton } from '@/components/ui/DocsButton' import InformationBox from '@/components/ui/InformationBox' import { useSchemasQuery } from '@/data/database/schemas-query' import { useTableQuery } from '@/data/tables/table-retrieve-query' @@ -32,7 +36,6 @@ import { useTablesQuery } from '@/data/tables/tables-query' import { useQuerySchemaState } from '@/hooks/misc/useSchemaQueryState' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { useConfirmOnClose } from '@/hooks/ui/useConfirmOnClose' -import { DOCS_URL } from '@/lib/constants' import { uuidv4 } from '@/lib/helpers' const EMPTY_STATE: ForeignKey = { @@ -258,61 +261,64 @@ export const ForeignKeySelector = ({ url="https://www.postgresql.org/docs/current/tutorial-fk.html" urlLabel="Postgres Foreign Key Documentation" /> - - updateSelectedSchema(value)} + className="gap-[2px]" + size="tiny" > - {sortedSchemas.map((schema) => { - return ( - } - > -
- {/* For aria searching to target the schema name instead of schema */} - {schema.name} - {schema.name} -
-
- ) - })} -
- - updateSelectedSchema(value)} + > + + + + + {sortedSchemas.map((schema) => ( + + {schema.name} + + ))} + + + + updateSelectedTable(Number(value))} - disabled={isLoadingSelectedTable} + className="gap-[2px]" + size="tiny" > - - --- - - {sortBy(tables, ['schema']).map((table) => { - return ( - } - > -
- {/* For aria searching to target the table name instead of schema */} - {table.name} - {table.schema} - {table.name} -
- - ) - })} - + updateSelectedTable(Number(value))} + disabled={isLoadingSelectedTable} + > + + + + + {sortBy(tables, ['schema']).map((table) => ( + +
+ {/* For aria searching to target the table name instead of schema */} + {table.name} + {table.schema} + {table.name} +
+
+ ))} +
+
+ {fk.schema && fk.table && ( <> @@ -331,10 +337,10 @@ export const ForeignKeySelector = ({ to reference to
-
+
{selectedSchema}.{table.name.length > 0 ? table.name : '[unnamed table]'}
-
+
{fk.schema}.{fk.table}
{fk.columns.length === 0 && ( @@ -347,73 +353,57 @@ export const ForeignKeySelector = ({ {fk.columns.map((_, idx) => (
- - updateSelectedColumn(idx, 'source', value) - } + onValueChange={(value) => updateSelectedColumn(idx, 'source', value)} > - 0 ? table.name : '[unnamed table]'}`} > - --- - - {(table?.columns ?? []) - .filter((x) => x.name.length !== 0) - .map((column) => ( - + + + + {(table?.columns ?? []) + .filter((x) => x.name.length !== 0) + .map((column) => ( + +
+ {column.name} + + {column.format === '' ? '-' : column.format} + +
+
+ ))} +
+ +
+
+ +
+
+ updateSelectedColumn(idx, 'target', value)} + > + + + + + {(selectedTable?.columns ?? []).map((column) => ( +
{column.name} {column.format === '' ? '-' : column.format}
- +
))} - -
-
- -
-
- - updateSelectedColumn(idx, 'target', value) - } - > - - --- - - {(selectedTable?.columns ?? []).map((column) => ( - -
- {column.name} - {column.format} -
-
- ))} -
+ +
- setDbRegion(region)} - descriptionText="Select a region close to your users for the best performance." + description="Select a region close to your users for the best performance." + className="gap-[2px]" + size="tiny" > - {Object.keys(AWS_REGIONS).map((option: string, i) => { - const label = Object.values(AWS_REGIONS)[i].displayName - return ( - ( - region icon - )} - > - {label} - - ) - })} - + setDbRegion(region)}> + + + + + {Object.keys(AWS_REGIONS).map((option: string, i) => { + const label = Object.values(AWS_REGIONS)[i].displayName + return ( + +
+ region icon + {label} +
+
+ ) + })} +
+
+
diff --git a/e2e/studio/features/table-editor.spec.ts b/e2e/studio/features/table-editor.spec.ts index e85d9f46ac4..1fc0bc481f3 100644 --- a/e2e/studio/features/table-editor.spec.ts +++ b/e2e/studio/features/table-editor.spec.ts @@ -1076,19 +1076,19 @@ testRunner('table editor', () => { .click() await page.getByRole('menuitem', { name: 'Edit table' }).click() - // Open foreign key selector - await page.getByRole('button', { name: 'Add foreign key relation' }).click() - - // Select schema (should default to public) - await expect(page.getByRole('button', { name: 'Select a schema' })).toContainText('public') - // Select target table const tableQueryPromise = waitForApiResponseWithTimeout(page, (response) => response.url().includes(`table-public-${targetTableName}`) ) - await page.getByRole('button', { name: 'Select a table to reference to' }).click() - await page.getByRole('menuitem', { name: `public ${targetTableName}` }).click() + // Open foreign key selector + await page.getByRole('button', { name: 'Add foreign key relation' }).click() + + // Select schema (should default to public) + await expect(page.getByRole('combobox', { name: 'Select a schema' })).toContainText('public') + + await page.getByRole('combobox', { name: 'Select a table to reference to' }).click() + await page.getByRole('option', { name: `public ${targetTableName}` }).click() // Wait for table columns to load await tableQueryPromise @@ -1099,15 +1099,12 @@ testRunner('table editor', () => { ).toBeVisible() // Select source column (id from source table) - await page.getByRole('button', { name: '---' }).first().click() - await page.getByRole('menuitem', { name: 'id int8' }).click() - - // Wait for the first dropdown to update - there should only be one '---' button left now - await expect(page.getByRole('button', { name: '---' })).toHaveCount(1) + await page.getByRole('combobox', { name: 'Column from public.pw_table_fk_source' }).click() + await page.getByRole('option', { name: 'id int8' }).click() // Select target column (id from target table) - await page.getByRole('button', { name: '---' }).first().click() - await page.getByRole('menuitem', { name: 'id int8' }).click() + await page.getByRole('combobox', { name: 'Column from public.pw_table_fk_target' }).click() + await page.getByRole('option', { name: 'id int8' }).click() // Verify cascade action options are visible await expect(page.getByText('Action if referenced row is updated')).toBeVisible() diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index 5fa82d80ca5..0c0058cc0f3 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -36,7 +36,6 @@ export * from './src/lib/utils' // DATA ENTRY -export * from './src/components/Listbox' export * from './src/components/Input' export * from './src/components/Toggle' export * from './src/components/ExpandingTextArea' diff --git a/packages/ui/src/components/Listbox/Listbox2.tsx b/packages/ui/src/components/Listbox/Listbox2.tsx deleted file mode 100644 index 74122084c94..00000000000 --- a/packages/ui/src/components/Listbox/Listbox2.tsx +++ /dev/null @@ -1,288 +0,0 @@ -'use client' - -import { flatten } from 'lodash' -import { Check } from 'lucide-react' -import { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui' -import React, { useEffect, useRef, useState } from 'react' - -import { FormLayout } from '../../lib/Layout/FormLayout/FormLayout' -import InputErrorIcon from '../../lib/Layout/InputErrorIcon' -import InputIconContainer from '../../lib/Layout/InputIconContainer' -import styleHandler from '../../lib/theme/styleHandler' -import { cn } from '../../lib/utils/cn' -import { SelectContext } from './SelectContext' - -export interface Props extends Omit, 'size'> { - className?: string - buttonClassName?: string - children: React.ReactNode - descriptionText?: string | React.ReactNode - error?: string - icon?: any - id?: string - label?: string | React.ReactNode - labelOptional?: string - layout?: 'horizontal' | 'vertical' - style?: React.CSSProperties - value?: any - reveal?: boolean - actions?: React.ReactNode - size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge' - defaultValue?: any - validation?: (x: any) => void - optionsWidth?: number - // override the button prop for onchange we only return a single value - // rather than a ChangeEvent - onChange?: (x: any) => void -} - -/** - * @deprecated Use `import { Select_shadcn_ } from "ui"` or follow ComboBox convention or use ./ui-patterns/multi-select - */ -function Listbox({ - children, - className, - buttonClassName, - descriptionText, - error, - icon, - id = '', - name = '', - label, - labelOptional, - layout, - value = undefined, - onChange, - onFocus, - onBlur, - style, - size = 'medium', - defaultValue, - disabled, - optionsWidth, -}: Props) { - const [selected, setSelected] = useState(undefined) - const [selectedNode, setSelectedNode] = useState({}) - - const __styles = styleHandler('listbox') - - const triggerRef = useRef(null) - - useEffect(() => { - if (value !== undefined) { - setSelected(value) - } - }, [value]) - - useEffect(() => { - // handle listbox options width size - - function handleResize() { - // Set window width/height to state - - // [Joshen] Note this causes some style conflicts if there are multiple listboxes - // rendered on the same page. All listbox option widths will be that of the latest - // listbox component that got rendered, rather than following its parent - document.documentElement.style.setProperty( - '--width-listbox', - `${optionsWidth ? optionsWidth : triggerRef.current?.offsetWidth}px` - ) - } - - // Add event listener - window.addEventListener('resize', handleResize) - - // Call handler right away so state gets updated with initial window size - handleResize() - - // Remove event listener on cleanup - return () => window.removeEventListener('resize', handleResize) - }, []) - - useEffect(() => { - const data: any = children - const content: any = flatten(data) - - function findNode(_value: any) { - return content.find((node: any) => node.props.value === _value) - } - - /* - * value prop overrides everything - */ - if (value) { - setSelected(value) - const node: any = findNode(value) - setSelectedNode(node?.props ? node.props : undefined) - return - } - - /* - * if no value prop, then use selected state - */ - if (selected) { - const node: any = findNode(selected) - setSelectedNode(node?.props ? node.props : undefined) - return - } else if (defaultValue) { - setSelected(defaultValue) - const node: any = findNode(defaultValue) - setSelectedNode(node?.props ? node.props : undefined) - return - } else { - /* - * if no selected value (including a `defaultvalue`), then use first child - */ - setSelectedNode(content[0]?.props) - return - } - }, [selected]) - - function handleOnChange(value: any) { - if (onChange) onChange(value) - setSelected(value) - } - - let selectClasses = [__styles.container, __styles.base, buttonClassName] - let addonBeforeClasses = [__styles.addOnBefore] - - if (error) selectClasses.push(__styles.variants.error) - if (!error) selectClasses.push(__styles.variants.standard) - // if (icon) selectClasses.push(SelectStyles['sbui-listbox--with-icon']) - if (icon) addonBeforeClasses.push(__styles.with_icon[size]) - // if (size) selectClasses.push(SelectStyles[`sbui-listbox--${size}`]) - if (size) selectClasses.push(__styles.size[size]) - // if (borderless) selectClasses.push(SelectStyles['sbui-listbox--borderless']) - if (disabled) selectClasses.push(__styles.disabled) - - return ( - - - - - - -
- - {children} - -
-
-
-
- ) -} - -interface OptionProps { - id?: string - value: any - label: string - disabled?: boolean - children?: React.ReactNode | (({ active, selected }: any) => React.ReactNode) - className?: string - addOnBefore?: ({ active, selected }: any) => React.ReactNode -} - -/** - * @deprecated Use ./Select_shadcn_ or follow ComboBox convention or use ./ui-patterns/multi-select - */ -function SelectOption({ - id, - value, - disabled = false, - children, - className = '', - addOnBefore, -}: OptionProps) { - const __styles = styleHandler('listbox') - - return ( - - {({ onChange, selected }) => { - const active = selected === value ? true : false - - return ( - (!disabled ? onChange(value) : {})} - > -
- {addOnBefore && addOnBefore({ active, selected })} - - {typeof children === 'function' ? children({ active, selected }) : children} - -
- - {active ? ( - - - ) : null} -
- ) - }} -
- ) -} - -Listbox.Option = SelectOption - -export default Listbox diff --git a/packages/ui/src/components/Listbox/SelectContext.tsx b/packages/ui/src/components/Listbox/SelectContext.tsx deleted file mode 100644 index 754cbddf4f6..00000000000 --- a/packages/ui/src/components/Listbox/SelectContext.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { createContext } from 'react' - -// Make sure the shape of the default value passed to -// createContext matches the shape that the consumers expect! -export const SelectContext = createContext({ - onChange: (_e: any) => {}, - selected: undefined, -}) diff --git a/packages/ui/src/components/Listbox/SelectStyled.module.css b/packages/ui/src/components/Listbox/SelectStyled.module.css deleted file mode 100644 index aa9941d73a9..00000000000 --- a/packages/ui/src/components/Listbox/SelectStyled.module.css +++ /dev/null @@ -1,145 +0,0 @@ -/* .sbui-listbox-container { - @apply relative; -} */ - -/* .sbui-listbox { - @apply block w-full bg-white pl-3 pr-10 py-2 text-sm rounded-md transition-all; - @apply text-gray-700 border border-solid border-gray-300; - @apply cursor-default focus:outline-none focus:ring-1 focus:ring-brand-600 focus:border-brand-600; - @apply dark:bg-transparent dark:text-white dark:border-gray-500; - @apply dark:focus:border-brand-300; - -webkit-appearance: none; - -moz-appearance: none; - text-indent: 1px; - text-overflow: ''; - box-shadow: 0 0 0 2px rgba(255, 255, 255, 0); - transition: box-shadow 0.3s ease-in-out; */ - -/* // temporary fix - // - // temporary fix for supabase apps - // tailwind @base styles adds a dropdown chevron - // using background image as default - // */ -/* background-image: none; -} */ - -.sbui-listbox:focus { - box-shadow: 0 0 0 2px rgba(62, 207, 142, 0.1); -} - -.sbui-listbox--error { - @apply border-red-500; -} - -.sbui-listbox--borderless { - @apply border-transparent shadow-none; -} - -/* - Select sizes -*/ - -/* .sbui-listbox--tiny { - @apply px-2.5 py-1.5 text-xs; -} -.sbui-listbox--small { - @apply px-3 py-2 text-sm leading-4; -} -.sbui-listbox--medium { - @apply px-4 py-2 text-sm; -} -.sbui-listbox--large { - @apply px-4 py-2 text-base; -} -.sbui-listbox--xlarge { - @apply px-6 py-3 text-base; -} */ - -/* .sbui-listbox-actions-container { - @apply absolute inset-y-0 right-0 pl-3 pr-1 mr-5 flex items-center; -} */ - -/* - Select icon -*/ - -/* .sbui-listbox--with-icon { - @apply pl-7; -} */ - -/* - Add on before -*/ - -/* .sbui-listbox-addonbefore { - @apply w-full flex flex-row items-center space-x-3; -} */ - -.sbui-listbox-label { - @apply truncate; -} - -/* - Select Chevron -*/ - -.sbui-listbox-chevron-container { - @apply absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none; -} - -.sbui-listbox-chevron { - @apply h-5 w-5 text-gray-400; -} - -/* - Option -*/ - -/* .sbui-listbox-option-container { - @apply list-none p-0 absolute mt-1 w-full bg-white dark:bg-gray-800 shadow-lg border border-solid border-control max-h-60 rounded-md py-1 text-base ring-1 ring-black/5 overflow-auto focus:outline-none sm:text-sm z-10; -} */ - -/* .sbui-listbox-option { - @apply cursor-pointer select-none relative py-2 pl-3 pr-9 text-gray-900 dark:text-white; -} */ - -/* .sbui-listbox-option--active { - @apply text-brand-600 dark:text-brand-600 bg-brand-600/20; -} - -.sbui-listbox-option--disabled { - @apply cursor-not-allowed opacity-50; -} */ - -/* .sbui-listbox-option__inner { - @apply flex items-center space-x-3; -} */ - -/* .sbui-listbox-option__check { - @apply absolute inset-y-0 right-0 flex items-center pr-3; -} - -.sbui-listbox-option__check--active { - @apply text-brand-600; -} */ - -.sbui-listbox-option__check__icon { - @apply h-5 w-5; -} - -/* - Transition -*/ - -.sbui-listbox-transition--leave { - @apply transition ease-in duration-100; -} - -.sbui-listbox-transition--leave-from { - @apply opacity-100; -} - -.sbui-listbox-transition--leave-to { - @apply opacity-0; -} diff --git a/packages/ui/src/components/Listbox/SelectStyled.test.jsx b/packages/ui/src/components/Listbox/SelectStyled.test.jsx deleted file mode 100644 index cdd90f083c6..00000000000 --- a/packages/ui/src/components/Listbox/SelectStyled.test.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import { render, screen } from '@testing-library/react' -import React from 'react' -import { describe, expect, it } from 'vitest' - -import Select from './index' - -describe('#Select', () => { - it.skip('should render select correctly', async () => { - render( - - ) - expect(screen.queryByTestId('form-select')).toBeInTheDocument() - }) - - it.skip('should have "form-select--error" class', () => { - render( - - ) - expect(screen.queryByTestId('form-select')).toHaveClass( - 'form-select border-solid form-select--error' - ) - }) -}) diff --git a/packages/ui/src/components/Listbox/index.tsx b/packages/ui/src/components/Listbox/index.tsx deleted file mode 100644 index 5bbc0008547..00000000000 --- a/packages/ui/src/components/Listbox/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import Listbox from './Listbox2' - -export default Listbox -export { default as Listbox } from './Listbox2'