mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
chore: migrate Listbox to Shadcn components (#45279)
## Problem We want to reduce the code we ship and maintain. ## Solution Migrate old `<Listbox />` usage to the new Shadcn component. ## Screenshots ### Billing Before: <img width="536" height="458" alt="image" src="https://github.com/user-attachments/assets/c8883bd8-cbbd-47cd-84a4-e37a36c05cd6" /> After: <img width="541" height="451" alt="image" src="https://github.com/user-attachments/assets/9c848b1b-e72a-413c-b264-48340ce0c7ef" /> ### Foreign Key Editor Before: <img width="463" height="995" alt="image" src="https://github.com/user-attachments/assets/4debeeeb-94bd-439d-8f57-a84a2ed5230a" /> <img width="428" height="241" alt="image" src="https://github.com/user-attachments/assets/81a21d58-c023-445a-a6e8-c9be22b53075" /> After: <img width="451" height="1007" alt="image" src="https://github.com/user-attachments/assets/18c781c0-4f52-4ca2-99e6-1fc34dc857e3" /> <img width="434" height="240" alt="image" src="https://github.com/user-attachments/assets/13fbb3ad-ef3c-499d-a27e-26cfae89ae44" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Replaced legacy dropdowns with a unified Select component and consistent form layout across payment method, org transfer, foreign-key/column selectors, region selection, and other dropdowns. * **Chores** * Removed the legacy listbox implementation and its public exports from the UI package. * **Tests** * Removed old listbox unit tests and updated e2e selectors to target the new Select-based controls. * **Style** * Deleted obsolete listbox-specific styles. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
a96d3d2b21
commit
718021fc7b
11 files changed
+301
-726
No files matched your search
+54
-38
@@ -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(
|
||||
<p className="text-sm text-foreground-light">Retrieving payment methods</p>
|
||||
</div>
|
||||
) : paymentMethods?.data && paymentMethods?.data.length > 0 && !setupNewPaymentMethod ? (
|
||||
<Listbox
|
||||
<FormItemLayout
|
||||
id="payment-method"
|
||||
isReactForm={false}
|
||||
layout={layout}
|
||||
label="Payment method"
|
||||
value={selectedPaymentMethod}
|
||||
onChange={onSelectPaymentMethod}
|
||||
className="flex items-center"
|
||||
className="gap-[2px]"
|
||||
size="tiny"
|
||||
>
|
||||
{paymentMethods?.data.map((method: any) => {
|
||||
const label = `•••• •••• •••• ${method.card.last4}`
|
||||
return (
|
||||
<Listbox.Option
|
||||
key={method.id}
|
||||
label={label}
|
||||
value={method.id}
|
||||
addOnBefore={() => {
|
||||
return (
|
||||
<img
|
||||
alt="Credit Card Brand"
|
||||
src={`${BASE_PATH}/img/payment-methods/${method.card.brand
|
||||
.replace(' ', '-')
|
||||
.toLowerCase()}.png`}
|
||||
width="32"
|
||||
/>
|
||||
)
|
||||
}}
|
||||
>
|
||||
<div>{label}</div>
|
||||
</Listbox.Option>
|
||||
)
|
||||
})}
|
||||
<div
|
||||
className="flex items-center px-3 py-2 space-x-2 transition cursor-pointer group hover:bg-surface-300"
|
||||
onClick={() => {
|
||||
setSetupNewPaymentMethod(true)
|
||||
<Select_Shadcn_
|
||||
value={selectedPaymentMethod}
|
||||
onValueChange={(value) => {
|
||||
if (value === 'new') {
|
||||
setSetupNewPaymentMethod(true)
|
||||
return
|
||||
}
|
||||
onSelectPaymentMethod(value)
|
||||
}}
|
||||
>
|
||||
<Plus size={16} />
|
||||
<p className="transition text-foreground-light group-hover:text-foreground">
|
||||
Add new payment method
|
||||
</p>
|
||||
</div>
|
||||
</Listbox>
|
||||
<SelectTrigger_Shadcn_ id="payment-method">
|
||||
<SelectValue_Shadcn_ className="flex gap-2" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
{paymentMethods?.data.map((method) => {
|
||||
const label = `•••• •••• •••• ${method.card?.last4}`
|
||||
return (
|
||||
<SelectItem_Shadcn_ key={method.id} value={method.id}>
|
||||
<div className="flex gap-2">
|
||||
<img
|
||||
alt="Credit Card Brand"
|
||||
src={`${BASE_PATH}/img/payment-methods/${method.card?.brand
|
||||
.replace(' ', '-')
|
||||
.toLowerCase()}.png`}
|
||||
width="32"
|
||||
/>
|
||||
{label}
|
||||
</div>
|
||||
</SelectItem_Shadcn_>
|
||||
)
|
||||
})}
|
||||
<SelectItem_Shadcn_ value="new">
|
||||
<div className="flex gap-2">
|
||||
<Plus size={16} />
|
||||
<p className="transition text-foreground-light group-hover:text-foreground">
|
||||
Add new payment method
|
||||
</p>
|
||||
</div>
|
||||
</SelectItem_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</FormItemLayout>
|
||||
) : null}
|
||||
|
||||
{stripePromise && setupIntent && customerProfile && (
|
||||
|
||||
+37
-22
@@ -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<string>()
|
||||
|
||||
const {
|
||||
mutate: transferProject,
|
||||
@@ -185,27 +197,30 @@ export const TransferProjectButton = () => {
|
||||
<InfoIcon /> You do not have any organizations you can transfer your project to.
|
||||
</div>
|
||||
) : (
|
||||
<Listbox
|
||||
label="Select Target Organization"
|
||||
<FormItemLayout
|
||||
id="organization"
|
||||
isReactForm={false}
|
||||
layout="vertical"
|
||||
value={selectedOrg}
|
||||
onChange={(slug) => setSelectedOrg(slug)}
|
||||
placeholder="Select Organization"
|
||||
label="Select Target Organization"
|
||||
className="gap-[2px]"
|
||||
size="tiny"
|
||||
>
|
||||
<Listbox.Option disabled key="no-results" label="Select Organization" value="">
|
||||
Select Organization
|
||||
</Listbox.Option>
|
||||
{organizations.map((x: any) => (
|
||||
<Listbox.Option
|
||||
key={x.id}
|
||||
label={x.name}
|
||||
value={x.slug}
|
||||
addOnBefore={() => <Users />}
|
||||
>
|
||||
{x.name}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
<Select_Shadcn_
|
||||
onValueChange={(slug) => setSelectedOrg(slug)}
|
||||
value={selectedOrg}
|
||||
>
|
||||
<SelectTrigger_Shadcn_ id="organization">
|
||||
<SelectValue_Shadcn_ placeholder="Select Organization" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
{organizations.map((x) => (
|
||||
<SelectItem_Shadcn_ key={x.id} value={x.slug}>
|
||||
{x.name}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
+155
-149
@@ -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"
|
||||
/>
|
||||
|
||||
<Listbox
|
||||
<FormItemLayout
|
||||
id="schema"
|
||||
isReactForm={false}
|
||||
layout="vertical"
|
||||
label="Select a schema"
|
||||
value={fk.schema}
|
||||
onChange={(value: string) => updateSelectedSchema(value)}
|
||||
className="gap-[2px]"
|
||||
size="tiny"
|
||||
>
|
||||
{sortedSchemas.map((schema) => {
|
||||
return (
|
||||
<Listbox.Option
|
||||
key={schema.id}
|
||||
value={schema.name}
|
||||
label={schema.name}
|
||||
className="min-w-96"
|
||||
addOnBefore={() => <Database size={16} strokeWidth={1.5} />}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* For aria searching to target the schema name instead of schema */}
|
||||
<span className="hidden">{schema.name}</span>
|
||||
<span className="text-foreground">{schema.name}</span>
|
||||
</div>
|
||||
</Listbox.Option>
|
||||
)
|
||||
})}
|
||||
</Listbox>
|
||||
|
||||
<Listbox
|
||||
<Select_Shadcn_
|
||||
value={fk.schema}
|
||||
onValueChange={(value) => updateSelectedSchema(value)}
|
||||
>
|
||||
<SelectTrigger_Shadcn_ id="schema">
|
||||
<SelectValue_Shadcn_ />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
{sortedSchemas.map((schema) => (
|
||||
<SelectItem_Shadcn_ key={schema.id} value={schema.name} className="min-w-96">
|
||||
{schema.name}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</FormItemLayout>
|
||||
<FormItemLayout
|
||||
id="table"
|
||||
isReactForm={false}
|
||||
layout="vertical"
|
||||
label="Select a table to reference to"
|
||||
value={selectedTable?.id ?? 1}
|
||||
onChange={(value: string) => updateSelectedTable(Number(value))}
|
||||
disabled={isLoadingSelectedTable}
|
||||
className="gap-[2px]"
|
||||
size="tiny"
|
||||
>
|
||||
<Listbox.Option key="empty" className="min-w-96" value={1} label="---">
|
||||
---
|
||||
</Listbox.Option>
|
||||
{sortBy(tables, ['schema']).map((table) => {
|
||||
return (
|
||||
<Listbox.Option
|
||||
key={table.id}
|
||||
value={table.id}
|
||||
label={table.name}
|
||||
className="min-w-96"
|
||||
addOnBefore={() => <Table size={16} strokeWidth={1.5} />}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* For aria searching to target the table name instead of schema */}
|
||||
<span className="hidden">{table.name}</span>
|
||||
<span className="text-foreground-lighter">{table.schema}</span>
|
||||
<span className="text-foreground">{table.name}</span>
|
||||
</div>
|
||||
</Listbox.Option>
|
||||
)
|
||||
})}
|
||||
</Listbox>
|
||||
<Select_Shadcn_
|
||||
value={selectedTable?.id !== undefined ? String(selectedTable.id) : undefined}
|
||||
onValueChange={(value) => updateSelectedTable(Number(value))}
|
||||
disabled={isLoadingSelectedTable}
|
||||
>
|
||||
<SelectTrigger_Shadcn_ id="table">
|
||||
<SelectValue_Shadcn_ />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
{sortBy(tables, ['schema']).map((table) => (
|
||||
<SelectItem_Shadcn_
|
||||
key={table.id}
|
||||
value={table.id.toString()}
|
||||
className="min-w-96"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* For aria searching to target the table name instead of schema */}
|
||||
<span className="hidden">{table.name}</span>
|
||||
<span className="text-foreground-lighter">{table.schema}</span>
|
||||
<span className="text-foreground">{table.name}</span>
|
||||
</div>
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</FormItemLayout>
|
||||
|
||||
{fk.schema && fk.table && (
|
||||
<>
|
||||
@@ -331,10 +337,10 @@ export const ForeignKeySelector = ({
|
||||
to reference to
|
||||
</label>
|
||||
<div className="grid grid-cols-10 gap-y-2">
|
||||
<div className="col-span-5 text-xs text-foreground-lighter">
|
||||
<div className="col-span-5 text-xs">
|
||||
{selectedSchema}.{table.name.length > 0 ? table.name : '[unnamed table]'}
|
||||
</div>
|
||||
<div className="col-span-4 text-xs text-foreground-lighter text-right">
|
||||
<div className="col-span-4 text-xs text-right">
|
||||
{fk.schema}.{fk.table}
|
||||
</div>
|
||||
{fk.columns.length === 0 && (
|
||||
@@ -347,73 +353,57 @@ export const ForeignKeySelector = ({
|
||||
{fk.columns.map((_, idx) => (
|
||||
<Fragment key={`${fk.schema}-${fk.table}-${idx}`}>
|
||||
<div className="col-span-4">
|
||||
<Listbox
|
||||
id="column"
|
||||
<Select_Shadcn_
|
||||
value={fk.columns[idx].source}
|
||||
onChange={(value: string) =>
|
||||
updateSelectedColumn(idx, 'source', value)
|
||||
}
|
||||
onValueChange={(value) => updateSelectedColumn(idx, 'source', value)}
|
||||
>
|
||||
<Listbox.Option
|
||||
key="empty"
|
||||
value={''}
|
||||
label="---"
|
||||
className="!w-[170px]"
|
||||
<SelectTrigger_Shadcn_
|
||||
aria-label={`Column from ${selectedSchema}.${table.name.length > 0 ? table.name : '[unnamed table]'}`}
|
||||
>
|
||||
---
|
||||
</Listbox.Option>
|
||||
{(table?.columns ?? [])
|
||||
.filter((x) => x.name.length !== 0)
|
||||
.map((column) => (
|
||||
<Listbox.Option
|
||||
key={column.id}
|
||||
value={column.name}
|
||||
label={column.name}
|
||||
className="!w-[170px]"
|
||||
>
|
||||
<SelectValue_Shadcn_ />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
{(table?.columns ?? [])
|
||||
.filter((x) => x.name.length !== 0)
|
||||
.map((column) => (
|
||||
<SelectItem_Shadcn_ key={column.id} value={column.name}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-foreground">{column.name}</span>
|
||||
<span className="text-foreground-lighter">
|
||||
{column.format === '' ? '-' : column.format}
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</div>
|
||||
<div className="col-span-1 flex justify-center items-center">
|
||||
<ArrowRight />
|
||||
</div>
|
||||
<div className="col-span-4">
|
||||
<Select_Shadcn_
|
||||
value={fk.columns[idx].target}
|
||||
onValueChange={(value) => updateSelectedColumn(idx, 'target', value)}
|
||||
>
|
||||
<SelectTrigger_Shadcn_
|
||||
aria-label={`Column from ${fk.schema}.${fk.table}`}
|
||||
>
|
||||
<SelectValue_Shadcn_ />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
{(selectedTable?.columns ?? []).map((column) => (
|
||||
<SelectItem_Shadcn_ key={column.id} value={column.name}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-foreground">{column.name}</span>
|
||||
<span className="text-foreground-lighter">
|
||||
{column.format === '' ? '-' : column.format}
|
||||
</span>
|
||||
</div>
|
||||
</Listbox.Option>
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</Listbox>
|
||||
</div>
|
||||
<div className="col-span-1 flex justify-center items-center">
|
||||
<ArrowRight />
|
||||
</div>
|
||||
<div className="col-span-4">
|
||||
<Listbox
|
||||
id="column"
|
||||
value={fk.columns[idx].target}
|
||||
onChange={(value: string) =>
|
||||
updateSelectedColumn(idx, 'target', value)
|
||||
}
|
||||
>
|
||||
<Listbox.Option
|
||||
key="empty"
|
||||
value={''}
|
||||
label="---"
|
||||
className="!w-[170px]"
|
||||
>
|
||||
---
|
||||
</Listbox.Option>
|
||||
{(selectedTable?.columns ?? []).map((column) => (
|
||||
<Listbox.Option
|
||||
key={column.id}
|
||||
value={column.name}
|
||||
label={column.name}
|
||||
className="!w-[170px]"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-foreground">{column.name}</span>
|
||||
<span className="text-foreground-lighter">{column.format}</span>
|
||||
</div>
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</div>
|
||||
<div className="col-span-1 flex justify-end items-center">
|
||||
<Button
|
||||
@@ -520,11 +510,12 @@ export const ForeignKeySelector = ({
|
||||
urlLabel="More information"
|
||||
/>
|
||||
|
||||
<Listbox
|
||||
<FormItemLayout
|
||||
id="updateAction"
|
||||
value={fk.updateAction}
|
||||
isReactForm={false}
|
||||
layout="vertical"
|
||||
label="Action if referenced row is updated"
|
||||
descriptionText={
|
||||
description={
|
||||
<p>
|
||||
{generateCascadeActionDescription(
|
||||
'update',
|
||||
@@ -533,45 +524,60 @@ export const ForeignKeySelector = ({
|
||||
)}
|
||||
</p>
|
||||
}
|
||||
onChange={(value: string) => updateCascadeAction('updateAction', value)}
|
||||
className="gap-[2px]"
|
||||
size="tiny"
|
||||
>
|
||||
{FOREIGN_KEY_CASCADE_OPTIONS.filter((option) =>
|
||||
['no-action', 'cascade', 'restrict'].includes(option.key)
|
||||
).map((option) => (
|
||||
<Listbox.Option key={option.key} value={option.value} label={option.label}>
|
||||
<p className="text-foreground">{option.label}</p>
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
|
||||
<Listbox
|
||||
<Select_Shadcn_
|
||||
value={fk.updateAction}
|
||||
onValueChange={(value) => updateCascadeAction('updateAction', value)}
|
||||
>
|
||||
<SelectTrigger_Shadcn_ id="updateAction">
|
||||
<SelectValue_Shadcn_ />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
{FOREIGN_KEY_CASCADE_OPTIONS.filter((option) =>
|
||||
['no-action', 'cascade', 'restrict'].includes(option.key)
|
||||
).map((option) => (
|
||||
<SelectItem_Shadcn_ key={option.key} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</FormItemLayout>
|
||||
<FormItemLayout
|
||||
id="deletionAction"
|
||||
value={fk.deletionAction}
|
||||
className="[&>div>label]:flex [&>div>label]:items-center"
|
||||
isReactForm={false}
|
||||
layout="vertical"
|
||||
label="Action if referenced row is removed"
|
||||
// @ts-ignore
|
||||
labelOptional={
|
||||
<DocsButton href={`${DOCS_URL}/guides/database/postgres/cascade-deletes`} />
|
||||
description={
|
||||
<p>
|
||||
{generateCascadeActionDescription(
|
||||
'delete',
|
||||
fk.deletionAction,
|
||||
`${fk.schema}.${fk.table}`
|
||||
)}
|
||||
</p>
|
||||
}
|
||||
descriptionText={
|
||||
<>
|
||||
<p>
|
||||
{generateCascadeActionDescription(
|
||||
'delete',
|
||||
fk.deletionAction,
|
||||
`${fk.schema}.${fk.table}`
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
onChange={(value: string) => updateCascadeAction('deletionAction', value)}
|
||||
className="gap-[2px]"
|
||||
size="tiny"
|
||||
>
|
||||
{FOREIGN_KEY_CASCADE_OPTIONS.map((option) => (
|
||||
<Listbox.Option key={option.key} value={option.value} label={option.label}>
|
||||
<p className="text-foreground">{option.label}</p>
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
<Select_Shadcn_
|
||||
value={fk.deletionAction}
|
||||
onValueChange={(value) => updateCascadeAction('deletionAction', value)}
|
||||
>
|
||||
<SelectTrigger_Shadcn_ id="deletionAction">
|
||||
<SelectValue_Shadcn_ />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
{FOREIGN_KEY_CASCADE_OPTIONS.map((option) => (
|
||||
<SelectItem_Shadcn_ key={option.key} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</FormItemLayout>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -3,7 +3,18 @@ import { useParams } from 'common'
|
||||
import { ChangeEvent, useEffect, useState } from 'react'
|
||||
import { AWS_REGIONS } from 'shared-data'
|
||||
import { toast } from 'sonner'
|
||||
import { Alert, Button, Checkbox, Input, Listbox } from 'ui'
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Checkbox,
|
||||
Input,
|
||||
Select_Shadcn_,
|
||||
SelectContent_Shadcn_,
|
||||
SelectItem_Shadcn_,
|
||||
SelectTrigger_Shadcn_,
|
||||
SelectValue_Shadcn_,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
import { isVercelUrl } from '@/components/interfaces/Integrations/Vercel/VercelIntegration.utils'
|
||||
import { Markdown } from '@/components/interfaces/Markdown'
|
||||
@@ -65,7 +76,7 @@ const CreateProject = () => {
|
||||
const [passwordStrengthMessage, setPasswordStrengthMessage] = useState('')
|
||||
const [passwordStrengthScore, setPasswordStrengthScore] = useState(-1)
|
||||
const [shouldRunMigrations, setShouldRunMigrations] = useState(true)
|
||||
const [dbRegion, setDbRegion] = useState(PROVIDERS.AWS.default_region.displayName)
|
||||
const [dbRegion, setDbRegion] = useState<string>(PROVIDERS.AWS.default_region.displayName)
|
||||
|
||||
const track = useTrack()
|
||||
const snapshot = useIntegrationInstallationSnapshot()
|
||||
@@ -276,33 +287,38 @@ const CreateProject = () => {
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<div className="mt-1">
|
||||
<Listbox
|
||||
<FormItemLayout
|
||||
id="region"
|
||||
isReactForm={false}
|
||||
layout="vertical"
|
||||
label="Region"
|
||||
type="select"
|
||||
value={dbRegion}
|
||||
onChange={(region) => 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 (
|
||||
<Listbox.Option
|
||||
key={option}
|
||||
label={label}
|
||||
value={label}
|
||||
addOnBefore={() => (
|
||||
<img
|
||||
alt="region icon"
|
||||
className="w-5 rounded-sm"
|
||||
src={`${BASE_PATH}/img/regions/${Object.values(AWS_REGIONS)[i].code}.svg`}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<span className="text-foreground">{label}</span>
|
||||
</Listbox.Option>
|
||||
)
|
||||
})}
|
||||
</Listbox>
|
||||
<Select_Shadcn_ value={dbRegion} onValueChange={(region) => setDbRegion(region)}>
|
||||
<SelectTrigger_Shadcn_ id="region">
|
||||
<SelectValue_Shadcn_ />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
{Object.keys(AWS_REGIONS).map((option: string, i) => {
|
||||
const label = Object.values(AWS_REGIONS)[i].displayName
|
||||
return (
|
||||
<SelectItem_Shadcn_ key={option} value={label}>
|
||||
<div className="flex gap-2">
|
||||
<img
|
||||
alt="region icon"
|
||||
className="w-5 rounded-sm"
|
||||
src={`${BASE_PATH}/img/regions/${Object.values(AWS_REGIONS)[i].code}.svg`}
|
||||
/>
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
</SelectItem_Shadcn_>
|
||||
)
|
||||
})}
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</FormItemLayout>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-2 pb-4">
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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<React.InputHTMLAttributes<HTMLButtonElement>, '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<HTMLButtonElement>
|
||||
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<any>({})
|
||||
|
||||
const __styles = styleHandler('listbox')
|
||||
|
||||
const triggerRef = useRef<HTMLButtonElement>(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 (
|
||||
<FormLayout
|
||||
label={label}
|
||||
labelOptional={labelOptional}
|
||||
layout={layout}
|
||||
id={id}
|
||||
error={error}
|
||||
descriptionText={descriptionText}
|
||||
className={className}
|
||||
style={style}
|
||||
size={size}
|
||||
>
|
||||
<DropdownMenuPrimitive.Root>
|
||||
<DropdownMenuPrimitive.Trigger asChild disabled={disabled}>
|
||||
<button
|
||||
data-size={size}
|
||||
ref={triggerRef}
|
||||
className={cn(selectClasses)}
|
||||
onBlur={onBlur}
|
||||
onFocus={onFocus}
|
||||
name={name}
|
||||
id={id}
|
||||
>
|
||||
<span className={cn(addonBeforeClasses)}>
|
||||
{icon && <InputIconContainer size={size} icon={icon} />}
|
||||
{selectedNode?.addOnBefore && <selectedNode.addOnBefore />}
|
||||
<span className={__styles.label}>{selectedNode?.label}</span>
|
||||
</span>
|
||||
<span className={__styles.chevron_container}>
|
||||
<svg
|
||||
className={__styles.chevron}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
{error && (
|
||||
<div className={__styles.actions_container}>
|
||||
{error && <InputErrorIcon size={size} />}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</DropdownMenuPrimitive.Trigger>
|
||||
<DropdownMenuPrimitive.Content
|
||||
sideOffset={6}
|
||||
loop={true}
|
||||
side={'bottom'}
|
||||
align="center"
|
||||
className={__styles.options_container}
|
||||
>
|
||||
<div>
|
||||
<SelectContext.Provider value={{ onChange: handleOnChange, selected }}>
|
||||
{children}
|
||||
</SelectContext.Provider>
|
||||
</div>
|
||||
</DropdownMenuPrimitive.Content>
|
||||
</DropdownMenuPrimitive.Root>
|
||||
</FormLayout>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<SelectContext.Consumer>
|
||||
{({ onChange, selected }) => {
|
||||
const active = selected === value ? true : false
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.Item
|
||||
key={id}
|
||||
className={cn(
|
||||
__styles.option,
|
||||
active ? __styles.option_active : ' ',
|
||||
disabled ? __styles.option_disabled : ' ',
|
||||
className
|
||||
)}
|
||||
onSelect={() => (!disabled ? onChange(value) : {})}
|
||||
>
|
||||
<div className={__styles.option_inner}>
|
||||
{addOnBefore && addOnBefore({ active, selected })}
|
||||
<span>
|
||||
{typeof children === 'function' ? children({ active, selected }) : children}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{active ? (
|
||||
<span
|
||||
className={cn(__styles.option_check, active ? __styles.option_check_active : '')}
|
||||
>
|
||||
<Check className={__styles.option_check_icon} aria-hidden="true" />
|
||||
</span>
|
||||
) : null}
|
||||
</DropdownMenuPrimitive.Item>
|
||||
)
|
||||
}}
|
||||
</SelectContext.Consumer>
|
||||
)
|
||||
}
|
||||
|
||||
Listbox.Option = SelectOption
|
||||
|
||||
export default Listbox
|
||||
@@ -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,
|
||||
})
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(
|
||||
<Select data-testid="form-select">
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
</Select>
|
||||
)
|
||||
expect(screen.queryByTestId('form-select')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it.skip('should have "form-select--error" class', () => {
|
||||
render(
|
||||
<Select isError data-testid="form-select">
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
</Select>
|
||||
)
|
||||
expect(screen.queryByTestId('form-select')).toHaveClass(
|
||||
'form-select border-solid form-select--error'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,4 +0,0 @@
|
||||
import Listbox from './Listbox2'
|
||||
|
||||
export default Listbox
|
||||
export { default as Listbox } from './Listbox2'
|
||||
Reference in new issue
Block a user