mirror of
https://github.com/supabase/supabase.git
synced 2026-05-06 22:18:00 +08:00
chore: migrate old <Select /> usage to the new Shadcn component (#45232)
## Problem We want to reduce the code we ship and maintain. ## Solution Migrate old `<Select />` usage to the new Shadcn component. ## Screenshots ### `www` Pricing Before: <img width="637" height="697" alt="image" src="https://github.com/user-attachments/assets/b6f261de-e587-411b-9408-faf94d709f1c" /> After: <img width="644" height="756" alt="image" src="https://github.com/user-attachments/assets/8cc4894c-64da-4e6a-960c-77cd162ac71d" /> ### Observability Before: <img width="1015" height="452" alt="image" src="https://github.com/user-attachments/assets/3d7e8613-e7a6-461d-a50d-e66c7c85fef1" /> After: <img width="833" height="467" alt="image" src="https://github.com/user-attachments/assets/98ace34f-25ec-48b5-aad3-fe812307b01d" /> ### Docs Realtime Used in pages: - https://supabase.com/docs/guides/realtime/postgres-changes - https://supabase.com/docs/guides/realtime/benchmarks Before: <img width="578" height="437" alt="image" src="https://github.com/user-attachments/assets/22fa0048-be07-42e0-9153-65171fa3ccb9" /> After: <img width="571" height="423" alt="image" src="https://github.com/user-attachments/assets/e0adbde9-0c6f-48da-b377-516392185fb0" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Updated dropdown/select controls across the app to a consistent, composable implementation * Replaced advanced JWT generator in docs with a simplified JWT generator component * **Chores** * Removed legacy select component, associated styles and exports * Updated theme and tests to align with the new select implementation <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,142 +0,0 @@
|
||||
import { KJUR } from 'jsrsasign'
|
||||
import { ChangeEvent, useState } from 'react'
|
||||
import { Button, Input, Select } from 'ui'
|
||||
import { CodeBlock } from 'ui-patterns/CodeBlock'
|
||||
|
||||
const JWT_HEADER = { alg: 'HS256', typ: 'JWT' }
|
||||
const now = new Date()
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
const fiveYears = new Date(now.getFullYear() + 5, now.getMonth(), now.getDate())
|
||||
|
||||
const anonToken = {
|
||||
role: 'anon',
|
||||
iss: 'supabase',
|
||||
iat: Math.floor(today.valueOf() / 1000),
|
||||
exp: Math.floor(fiveYears.valueOf() / 1000),
|
||||
}
|
||||
const serviceToken = {
|
||||
role: 'service_role',
|
||||
iss: 'supabase',
|
||||
iat: Math.floor(today.valueOf() / 1000),
|
||||
exp: Math.floor(fiveYears.valueOf() / 1000),
|
||||
}
|
||||
|
||||
const generateRandomString = (length: number) => {
|
||||
const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
let result = ''
|
||||
|
||||
/**
|
||||
* The number of possible Uint8 integers is 256. Since the length of `CHARS`
|
||||
* doesn't fit exactly into 256, simply taking the modulus would create an
|
||||
* uneven distribution that favors the earlier characters. To make a truly
|
||||
* uniform distribution, we have to discard everything above the last full
|
||||
* cycle, and pick again.
|
||||
*
|
||||
* The minus 1 is to account for 0-indexing.
|
||||
*/
|
||||
const MAX = Math.floor(256 / CHARS.length) * CHARS.length - 1
|
||||
|
||||
const randomUInt8Array = new Uint8Array(1)
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
let randomNumber: number
|
||||
do {
|
||||
crypto.getRandomValues(randomUInt8Array)
|
||||
randomNumber = randomUInt8Array[0]
|
||||
/**
|
||||
* Keep picking until we get a number in the valid range.
|
||||
*/
|
||||
} while (randomNumber > MAX)
|
||||
|
||||
result += CHARS[randomNumber % CHARS.length]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export default function JwtGenerator() {
|
||||
const secret = generateRandomString(40)
|
||||
|
||||
const [jwtSecret, setJwtSecret] = useState(secret)
|
||||
const [token, setToken] = useState(anonToken)
|
||||
const [signedToken, setSignedToken] = useState('')
|
||||
const [err, setErr] = useState<string>('')
|
||||
|
||||
const handleKeySelection = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const val = e.target.value
|
||||
if (val == 'service') setToken(serviceToken)
|
||||
else setToken(anonToken)
|
||||
}
|
||||
|
||||
const handleClaimsChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
try {
|
||||
const newTok = JSON.parse(e.target.value)
|
||||
setToken(newTok)
|
||||
setErr('')
|
||||
} catch (err) {
|
||||
const errMessage =
|
||||
!!err && typeof err === 'object' && 'message' in err && typeof err.message === 'string'
|
||||
? err.message
|
||||
: ''
|
||||
setErr('Not a valid JSON body' + (errMessage ? `: ${errMessage}` : ''))
|
||||
}
|
||||
}
|
||||
|
||||
const generate = () => {
|
||||
const signedJWT = KJUR.jws.JWS.sign(null, JWT_HEADER, token, jwtSecret)
|
||||
setSignedToken(signedJWT)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border rounded-lg p-4">
|
||||
<div className="grid mb-8">
|
||||
<label htmlFor="secret">JWT Secret:</label>
|
||||
<Input
|
||||
id="secret"
|
||||
type="text"
|
||||
placeholder="JWT Secret (at least 32 characters)"
|
||||
value={jwtSecret}
|
||||
style={{ fontFamily: 'monospace' }}
|
||||
onChange={(e) => setJwtSecret(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid mb-8">
|
||||
<label htmlFor="service">Key:</label>
|
||||
<Select id="service" style={{ fontFamily: 'monospace' }} onChange={handleKeySelection}>
|
||||
<Select.Option value="anon">ANON_KEY</Select.Option>
|
||||
<Select.Option value="service">SERVICE_KEY</Select.Option>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="grid mb-8">
|
||||
<label htmlFor="token">The JWT will be generated from this info:</label>
|
||||
<Input.TextArea
|
||||
key={JSON.stringify(token)}
|
||||
id="token"
|
||||
type="text"
|
||||
rows={6}
|
||||
placeholder="A valid JWT Token"
|
||||
defaultValue={JSON.stringify(token, null, 2)}
|
||||
style={{ fontFamily: 'monospace' }}
|
||||
onChange={handleClaimsChange}
|
||||
/>
|
||||
{err && (
|
||||
<span className="text-sm text-destructive-600">Input must be valid JSON. {err}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button type="primary" onClick={generate}>
|
||||
Generate JWT
|
||||
</Button>
|
||||
|
||||
{signedToken && (
|
||||
<div className="mt-8">
|
||||
<h4>Generated Token:</h4>
|
||||
<CodeBlock language="bash" className="relative font-mono">
|
||||
{signedToken}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -3,17 +3,8 @@
|
||||
import dynamic from 'next/dynamic'
|
||||
import { Suspense } from 'react'
|
||||
|
||||
const DynamicJwtGenerator = dynamic(() => import('./JwtGenerator'), { ssr: false })
|
||||
const DynamicJwtGeneratorSimple = dynamic(() => import('./JwtGeneratorSimple'), { ssr: false })
|
||||
|
||||
const JwtGenerator = () => {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<DynamicJwtGenerator />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
const JwtGeneratorSimple = () => {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
@@ -22,4 +13,4 @@ const JwtGeneratorSimple = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export { JwtGenerator, JwtGeneratorSimple }
|
||||
export { JwtGeneratorSimple }
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import throughputTable from '~/data/realtime/throughput.json'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Button, Collapsible, Select } from 'ui'
|
||||
import throughputTable from '~/data/realtime/throughput.json'
|
||||
import {
|
||||
Button,
|
||||
Collapsible,
|
||||
Label_Shadcn_,
|
||||
Select_Shadcn_,
|
||||
SelectContent_Shadcn_,
|
||||
SelectItem_Shadcn_,
|
||||
SelectTrigger_Shadcn_,
|
||||
SelectValue_Shadcn_,
|
||||
} from 'ui'
|
||||
|
||||
export default function RealtimeLimitsEstimater({}) {
|
||||
const findTableValue = ({ computeAddOn, filters, rls, concurrency }) => {
|
||||
@@ -23,29 +32,28 @@ export default function RealtimeLimitsEstimater({}) {
|
||||
|
||||
const [expandPreview, setExpandPreview] = useState(false)
|
||||
|
||||
const handleComputeAddOnSelection = (e) => {
|
||||
const val = e.target.value
|
||||
const handleComputeAddOnSelection = (val) => {
|
||||
setComputeAddOn(val)
|
||||
setConcurrency(500)
|
||||
setLimits(findTableValue({ computeAddOn: val, filters, rls, concurrency: 500 }))
|
||||
}
|
||||
|
||||
const handleFiltersSelection = (e) => {
|
||||
const val = e.target.value.toLowerCase() === 'true'
|
||||
const handleFiltersSelection = (value) => {
|
||||
const val = value.toLowerCase() === 'true'
|
||||
setFilters(val)
|
||||
setConcurrency(500)
|
||||
setLimits(findTableValue({ computeAddOn, filters: val, rls, concurrency: 500 }))
|
||||
}
|
||||
|
||||
const handleRLSSelection = (e) => {
|
||||
const val = e.target.value.toLowerCase() === 'true'
|
||||
const handleRLSSelection = (value) => {
|
||||
const val = value.toLowerCase() === 'true'
|
||||
setRLS(val)
|
||||
setConcurrency(500)
|
||||
setLimits(findTableValue({ computeAddOn, filters, rls: val, concurrency: 500 }))
|
||||
}
|
||||
|
||||
const handleConcurrencySelection = (e) => {
|
||||
const val = parseInt(e.target.value)
|
||||
const handleConcurrencySelection = (value) => {
|
||||
const val = parseInt(value)
|
||||
setConcurrency(val)
|
||||
setLimits(findTableValue({ computeAddOn, filters, rls, concurrency: val }))
|
||||
}
|
||||
@@ -55,51 +63,64 @@ export default function RealtimeLimitsEstimater({}) {
|
||||
<h4>Set your expected parameters</h4>
|
||||
<div className="grid mb-8 gap-y-8 gap-x-8 grid-cols-2 xl:grid-cols-4">
|
||||
<div>
|
||||
<label htmlFor="computeAddOn">Compute:</label>
|
||||
<Select id="computeAddOn" className="font-mono" onChange={handleComputeAddOnSelection}>
|
||||
<Select.Option value="micro">Micro</Select.Option>
|
||||
<Select.Option value="small">Small to medium</Select.Option>
|
||||
<Select.Option value="large">Large to 16XL</Select.Option>
|
||||
</Select>
|
||||
<Label_Shadcn_ htmlFor="computeAddOn">Compute:</Label_Shadcn_>
|
||||
<Select_Shadcn_ onValueChange={handleComputeAddOnSelection} value={computeAddOn}>
|
||||
<SelectTrigger_Shadcn_ id="computeAddOn">
|
||||
<SelectValue_Shadcn_ className="font-mono" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="micro">Micro</SelectItem_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="small">Small to medium</SelectItem_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="large">Large to 16XL</SelectItem_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="filters">Filters:</label>
|
||||
<Select
|
||||
id="filters"
|
||||
className="font-mono"
|
||||
disabled={true}
|
||||
onChange={handleFiltersSelection}
|
||||
<Label_Shadcn_ htmlFor="filters">Filters:</Label_Shadcn_>
|
||||
<Select_Shadcn_
|
||||
onValueChange={handleFiltersSelection}
|
||||
value={filters.toString()}
|
||||
disabled
|
||||
>
|
||||
<Select.Option value="false">No</Select.Option>
|
||||
<Select.Option value="true">Yes</Select.Option>
|
||||
</Select>
|
||||
<SelectTrigger_Shadcn_ id="filters">
|
||||
<SelectValue_Shadcn_ className="font-mono" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="false">No</SelectItem_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="true">Yes</SelectItem_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="rls">RLS:</label>
|
||||
<Select id="rls" className="font-mono" onChange={handleRLSSelection}>
|
||||
<Select.Option value="false">No</Select.Option>
|
||||
<Select.Option value="true">Yes</Select.Option>
|
||||
</Select>
|
||||
<Label_Shadcn_ htmlFor="rls">RLS:</Label_Shadcn_>
|
||||
<Select_Shadcn_ onValueChange={handleRLSSelection} value={rls.toString()}>
|
||||
<SelectTrigger_Shadcn_ id="rls">
|
||||
<SelectValue_Shadcn_ className="font-mono" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="false">No</SelectItem_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="true">Yes</SelectItem_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="concurrency">Connected clients:</label>
|
||||
<Select id="concurrency" className="font-mono" onChange={handleConcurrencySelection}>
|
||||
{throughputTable
|
||||
.filter(
|
||||
(l) => l.computeAddOn === computeAddOn && l.filters === filters && l.rls === rls
|
||||
)
|
||||
.map((l) => (
|
||||
<Select.Option
|
||||
value={l.concurrency.toString()}
|
||||
selected={l.concurrency === concurrency}
|
||||
>
|
||||
{Intl.NumberFormat().format(l.concurrency)}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Label_Shadcn_ htmlFor="concurrency">Connected clients:</Label_Shadcn_>
|
||||
<Select_Shadcn_ onValueChange={handleConcurrencySelection} value={concurrency.toString()}>
|
||||
<SelectTrigger_Shadcn_ id="concurrency">
|
||||
<SelectValue_Shadcn_ className="font-mono" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
{throughputTable
|
||||
.filter(
|
||||
(l) => l.computeAddOn === computeAddOn && l.filters === filters && l.rls === rls
|
||||
)
|
||||
.map((l) => (
|
||||
<SelectItem_Shadcn_ key={l.concurrency} value={l.concurrency.toString()}>
|
||||
{Intl.NumberFormat().format(l.concurrency)}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
import { ArrowDown, Check, X } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { Badge, Button } from 'ui'
|
||||
import { Admonition, type AdmonitionProps } from 'ui-patterns/admonition'
|
||||
import { GlassPanel } from 'ui-patterns/GlassPanel'
|
||||
import { IconPanel } from 'ui-patterns/IconPanel'
|
||||
import SqlToRest from 'ui-patterns/SqlToRest'
|
||||
import { Heading } from 'ui/src/components/CustomHTMLElements'
|
||||
import { AiPromptsIndex } from '~/app/guides/getting-started/ai-prompts/[slug]/AiPromptsIndex'
|
||||
import { AiSkillsIndex } from '~/app/guides/getting-started/ai-skills/AiSkillsIndex'
|
||||
import { AppleSecretGenerator } from '~/components/AppleSecretGenerator'
|
||||
@@ -13,15 +5,15 @@ import AuthProviders from '~/components/AuthProviders'
|
||||
import { AuthSmsProviderConfig } from '~/components/AuthSmsProviderConfig'
|
||||
import { CostWarning } from '~/components/AuthSmsProviderConfig/AuthSmsProviderConfig.Warnings'
|
||||
import ButtonCard from '~/components/ButtonCard'
|
||||
import { ComputeDiskLimitsTable } from '~/components/ComputeDiskLimitsTable'
|
||||
import { Extensions } from '~/components/Extensions'
|
||||
import Image, { type ImageProps } from '~/components/Image'
|
||||
import { JwtGenerator, JwtGeneratorSimple } from '~/components/JwtGenerator'
|
||||
import { JwtGeneratorSimple } from '~/components/JwtGenerator'
|
||||
import { MetricsStackCards } from '~/components/MetricsStackCards'
|
||||
import { NavData } from '~/components/NavData'
|
||||
import { Price } from '~/components/Price'
|
||||
import { ProjectConfigVariables } from '~/components/ProjectConfigVariables'
|
||||
import { RealtimeLimitsEstimator } from '~/components/RealtimeLimitsEstimator'
|
||||
import { ComputeDiskLimitsTable } from '~/components/ComputeDiskLimitsTable'
|
||||
import { RegionsList, SmartRegionsList } from '~/components/RegionsList'
|
||||
import { SharedData } from '~/components/SharedData'
|
||||
import StepHikeCompact from '~/components/StepHikeCompact'
|
||||
@@ -32,6 +24,15 @@ import { CodeBlock } from '~/features/ui/CodeBlock/CodeBlock'
|
||||
import InfoTooltip from '~/features/ui/InfoTooltip'
|
||||
import { ShowUntil } from '~/features/ui/ShowUntil'
|
||||
import { TabPanel, Tabs } from '~/features/ui/Tabs'
|
||||
import { ArrowDown, Check, X } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { Badge, Button } from 'ui'
|
||||
import { Admonition, type AdmonitionProps } from 'ui-patterns/admonition'
|
||||
import { GlassPanel } from 'ui-patterns/GlassPanel'
|
||||
import { IconPanel } from 'ui-patterns/IconPanel'
|
||||
import SqlToRest from 'ui-patterns/SqlToRest'
|
||||
import { Heading } from 'ui/src/components/CustomHTMLElements'
|
||||
|
||||
import { ErrorCodes } from '../ui/ErrorCodes'
|
||||
import { McpConfigPanel } from '../ui/McpConfigPanel'
|
||||
|
||||
@@ -64,7 +65,6 @@ const components = {
|
||||
IconPanel,
|
||||
IconX: X,
|
||||
Image: (props: ImageProps) => <Image className="rounded-md w-full" {...props} />,
|
||||
JwtGenerator,
|
||||
JwtGeneratorSimple,
|
||||
Link,
|
||||
McpConfigPanel,
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import type { RenderEditCellProps } from 'react-data-grid'
|
||||
import { Select } from 'ui'
|
||||
import {
|
||||
Select_Shadcn_,
|
||||
SelectContent_Shadcn_,
|
||||
SelectGroup_Shadcn_,
|
||||
SelectItem_Shadcn_,
|
||||
SelectTrigger_Shadcn_,
|
||||
SelectValue_Shadcn_,
|
||||
} from 'ui'
|
||||
|
||||
interface Props<TRow, TSummaryRow = unknown> extends RenderEditCellProps<TRow, TSummaryRow> {
|
||||
isNullable?: boolean
|
||||
@@ -15,8 +22,7 @@ export const BooleanEditor = <TRow, TSummaryRow = unknown>({
|
||||
const value = row[column.key as keyof TRow] as unknown as string
|
||||
|
||||
const onBlur = () => onClose(false)
|
||||
const onChange = (event: any) => {
|
||||
const value = event.target.value
|
||||
const onChange = (value: string) => {
|
||||
if (value === 'null') {
|
||||
onRowChange({ ...row, [column.key]: null }, true)
|
||||
} else {
|
||||
@@ -25,19 +31,21 @@ export const BooleanEditor = <TRow, TSummaryRow = unknown>({
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
autoFocus
|
||||
id="boolean-editor"
|
||||
<Select_Shadcn_
|
||||
name="boolean-editor"
|
||||
size="small"
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
defaultValue={value === null || value === undefined ? 'null' : value.toString()}
|
||||
style={{ width: `${column.width}px` }}
|
||||
onValueChange={onChange}
|
||||
>
|
||||
<Select.Option value="true">TRUE</Select.Option>
|
||||
<Select.Option value="false">FALSE</Select.Option>
|
||||
{isNullable && <Select.Option value="null">NULL</Select.Option>}
|
||||
</Select>
|
||||
<SelectTrigger_Shadcn_ onBlur={onBlur} style={{ width: `${column.width}px` }}>
|
||||
<SelectValue_Shadcn_ id="boolean-editor" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectGroup_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="true">TRUE</SelectItem_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="false">FALSE</SelectItem_Shadcn_>
|
||||
{isNullable ? <SelectItem_Shadcn_ value="null">NULL</SelectItem_Shadcn_> : null}
|
||||
</SelectGroup_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import type { RenderEditCellProps } from 'react-data-grid'
|
||||
import { Select } from 'ui'
|
||||
import {
|
||||
Select_Shadcn_,
|
||||
SelectContent_Shadcn_,
|
||||
SelectGroup_Shadcn_,
|
||||
SelectItem_Shadcn_,
|
||||
SelectTrigger_Shadcn_,
|
||||
SelectValue_Shadcn_,
|
||||
} from 'ui'
|
||||
|
||||
interface SelectEditorProps<TRow, TSummaryRow = unknown> extends RenderEditCellProps<
|
||||
TRow,
|
||||
TSummaryRow
|
||||
> {
|
||||
isNullable?: boolean
|
||||
options: { label: string; _value: string }[]
|
||||
options: { label: string; value: string }[]
|
||||
}
|
||||
|
||||
export function SelectEditor<TRow, TSummaryRow = unknown>({
|
||||
@@ -19,11 +26,11 @@ export function SelectEditor<TRow, TSummaryRow = unknown>({
|
||||
}: SelectEditorProps<TRow, TSummaryRow>) {
|
||||
const value = row[column.key as keyof TRow] as unknown as string
|
||||
|
||||
function onChange(event: any) {
|
||||
if (!event.target.value || event.target.value == '') {
|
||||
function onChange(value: string) {
|
||||
if (!value || value == '') {
|
||||
onRowChange({ ...row, [column.key]: null }, true)
|
||||
} else {
|
||||
onRowChange({ ...row, [column.key]: event.target.value }, true)
|
||||
onRowChange({ ...row, [column.key]: value }, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,24 +39,20 @@ export function SelectEditor<TRow, TSummaryRow = unknown>({
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
autoFocus
|
||||
id="select-editor"
|
||||
name="select-editor"
|
||||
size="small"
|
||||
defaultValue={value ?? ''}
|
||||
className="sb-grid-select-editor !gap-2"
|
||||
style={{ width: `${column.width}px` }}
|
||||
// @ts-ignore
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
>
|
||||
{isNullable && <Select.Option value="">NULL</Select.Option>}
|
||||
{options.map(({ label, _value }) => (
|
||||
<Select.Option key={_value} value={_value} selected={_value === value}>
|
||||
{label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Select_Shadcn_ name="select-editor" defaultValue={value ?? ''} onValueChange={onChange}>
|
||||
<SelectTrigger_Shadcn_ onBlur={onBlur} style={{ width: `${column.width}px` }}>
|
||||
<SelectValue_Shadcn_ id="select-editor" placeholder="NULL" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectGroup_Shadcn_>
|
||||
{isNullable ? <SelectItem_Shadcn_ value={null as any}>NULL</SelectItem_Shadcn_> : null}
|
||||
{options.map(({ label, value }) => (
|
||||
<SelectItem_Shadcn_ key={value} value={value}>
|
||||
{label}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectGroup_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,8 +13,15 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
Input,
|
||||
Select,
|
||||
Input_Shadcn_,
|
||||
Select_Shadcn_,
|
||||
SelectContent_Shadcn_,
|
||||
SelectGroup_Shadcn_,
|
||||
SelectItem_Shadcn_,
|
||||
SelectTrigger_Shadcn_,
|
||||
SelectValue_Shadcn_,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
import { DatePickerValue, LogsDatePicker } from '../Settings/Logs/Logs.DatePickers'
|
||||
import { REPORTS_DATEPICKER_HELPERS } from './Reports.constants'
|
||||
@@ -291,52 +298,82 @@ const ReportFilterBar = ({
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align={filters.length > 0 ? 'end' : 'start'} className="p-0 w-60">
|
||||
<div className="flex flex-col gap-3 p-3">
|
||||
<Select
|
||||
size="tiny"
|
||||
value={addFilterValues.key}
|
||||
onChange={(e) => {
|
||||
setAddFilterValues((prev) => ({ ...prev, key: e.target.value }))
|
||||
}}
|
||||
<FormItemLayout
|
||||
isReactForm={false}
|
||||
layout="vertical"
|
||||
label="Attribute Filter"
|
||||
className="gap-[2px]"
|
||||
>
|
||||
{filterKeys.map((key) => (
|
||||
<Select.Option key={key} value={key}>
|
||||
{key}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Select
|
||||
size="tiny"
|
||||
value={addFilterValues.compare}
|
||||
onChange={(e) => {
|
||||
setAddFilterValues((prev) => ({
|
||||
...prev,
|
||||
compare: e.target.value as ReportFilterItem['compare'],
|
||||
}))
|
||||
}}
|
||||
>
|
||||
<Select_Shadcn_
|
||||
value={addFilterValues.key}
|
||||
onValueChange={(value: string) =>
|
||||
setAddFilterValues((prev) => ({ ...prev, key: value }))
|
||||
}
|
||||
>
|
||||
<SelectTrigger_Shadcn_>
|
||||
<SelectValue_Shadcn_ placeholder="---" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectGroup_Shadcn_>
|
||||
{filterKeys.map((key) => (
|
||||
<SelectItem_Shadcn_ key={key} value={key}>
|
||||
{key}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectGroup_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</FormItemLayout>
|
||||
<FormItemLayout
|
||||
isReactForm={false}
|
||||
layout="vertical"
|
||||
label="Comparison"
|
||||
className="gap-[2px]"
|
||||
>
|
||||
{['is', 'matches'].map((value) => (
|
||||
<Select.Option key={value} value={value}>
|
||||
{value}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Input
|
||||
size="tiny"
|
||||
>
|
||||
<Select_Shadcn_
|
||||
value={addFilterValues.compare}
|
||||
onValueChange={(value: string) =>
|
||||
setAddFilterValues((prev) => ({
|
||||
...prev,
|
||||
compare: value as ReportFilterItem['compare'],
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger_Shadcn_>
|
||||
<SelectValue_Shadcn_ placeholder="---" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectGroup_Shadcn_>
|
||||
{['is', 'matches'].map((value) => (
|
||||
<SelectItem_Shadcn_ key={value} value={value}>
|
||||
{value}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectGroup_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</FormItemLayout>
|
||||
<FormItemLayout
|
||||
isReactForm={false}
|
||||
layout="vertical"
|
||||
label="Value"
|
||||
className="gap-[2px]"
|
||||
placeholder={
|
||||
addFilterValues.compare === 'matches'
|
||||
? 'Provide a regex expression'
|
||||
: 'Provide a string'
|
||||
}
|
||||
onChange={(e) => {
|
||||
setAddFilterValues((prev) => ({ ...prev, value: e.target.value }))
|
||||
}}
|
||||
/>
|
||||
size="tiny"
|
||||
>
|
||||
<Input_Shadcn_
|
||||
placeholder={
|
||||
addFilterValues.compare === 'matches'
|
||||
? 'Provide a regex expression'
|
||||
: 'Provide a string'
|
||||
}
|
||||
value={addFilterValues.value}
|
||||
onChange={(e) => {
|
||||
setAddFilterValues((prev) => ({ ...prev, value: e.target.value }))
|
||||
}}
|
||||
/>
|
||||
</FormItemLayout>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 border-t border-default p-2">
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { noop } from 'lodash'
|
||||
import { Select } from 'ui'
|
||||
import {
|
||||
Select_Shadcn_,
|
||||
SelectContent_Shadcn_,
|
||||
SelectGroup_Shadcn_,
|
||||
SelectItem_Shadcn_,
|
||||
SelectTrigger_Shadcn_,
|
||||
SelectValue_Shadcn_,
|
||||
} from 'ui'
|
||||
|
||||
import { POSTGRES_DATA_TYPES } from '../SidePanelEditor.constants'
|
||||
import type { ColumnField } from '../SidePanelEditor.types'
|
||||
@@ -36,21 +43,28 @@ const ColumnDefaultValue = ({
|
||||
|
||||
if (enumType !== undefined) {
|
||||
return (
|
||||
<Select
|
||||
label="Default Value"
|
||||
layout="vertical"
|
||||
value={formattedValue}
|
||||
onChange={(event: any) => onUpdateField({ defaultValue: event.target.value })}
|
||||
>
|
||||
<Select.Option key="empty-enum" value="">
|
||||
NULL
|
||||
</Select.Option>
|
||||
{enumValues.map((value: string) => (
|
||||
<Select.Option key={value} value={value}>
|
||||
{value}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<>
|
||||
<label className="block text-foreground-light">Default Value</label>
|
||||
<Select_Shadcn_
|
||||
name="select-editor"
|
||||
value={formattedValue}
|
||||
onValueChange={(value) => onUpdateField({ defaultValue: value })}
|
||||
>
|
||||
<SelectTrigger_Shadcn_>
|
||||
<SelectValue_Shadcn_ id="select-editor" placeholder="NULL" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectGroup_Shadcn_>
|
||||
<SelectItem_Shadcn_ value={null as any}>NULL</SelectItem_Shadcn_>
|
||||
{enumValues.map((value) => (
|
||||
<SelectItem_Shadcn_ key={value} value={value}>
|
||||
{value}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectGroup_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
Input,
|
||||
Select,
|
||||
Select_Shadcn_,
|
||||
SelectContent_Shadcn_,
|
||||
SelectGroup_Shadcn_,
|
||||
@@ -76,24 +75,34 @@ export const InputField = ({
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Select
|
||||
size="medium"
|
||||
<FormItemLayout
|
||||
isReactForm={false}
|
||||
layout="horizontal"
|
||||
value={field.value ?? ''}
|
||||
label={field.name}
|
||||
labelOptional={field.format}
|
||||
descriptionText={field.comment}
|
||||
disabled={!isEditable}
|
||||
error={errors[field.name]}
|
||||
onChange={(event: any) => onUpdateField({ [field.name]: event.target.value })}
|
||||
description={field.comment}
|
||||
className="[&>div:first-child>span]:text-foreground-lighter"
|
||||
>
|
||||
<Select.Option value="">---</Select.Option>
|
||||
{field.enums.map((value: string) => (
|
||||
<Select.Option key={value} value={value}>
|
||||
{value}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Select_Shadcn_
|
||||
value={field.value ?? ''}
|
||||
onValueChange={(value: string) => onUpdateField({ [field.name]: value })}
|
||||
disabled={!isEditable}
|
||||
>
|
||||
<SelectTrigger_Shadcn_>
|
||||
<SelectValue_Shadcn_ placeholder="---" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectGroup_Shadcn_>
|
||||
<SelectItem_Shadcn_ value={null as any}>---</SelectItem_Shadcn_>
|
||||
{field.enums.map((value) => (
|
||||
<SelectItem_Shadcn_ key={value} value={value}>
|
||||
{value}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectGroup_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { plans } from 'shared-data/plans'
|
||||
import { pricing } from 'shared-data/pricing'
|
||||
import { Button, Select, cn } from 'ui'
|
||||
import { PricingTableRowDesktop, PricingTableRowMobile } from '~/components/Pricing/PricingTableRow'
|
||||
import Solutions from '~/data/MainProducts'
|
||||
import { Organization } from '~/data/organizations'
|
||||
import { useSendTelemetryEvent } from '~/lib/telemetry'
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import { plans } from 'shared-data/plans'
|
||||
import { pricing } from 'shared-data/pricing'
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
Select_Shadcn_,
|
||||
SelectContent_Shadcn_,
|
||||
SelectGroup_Shadcn_,
|
||||
SelectItem_Shadcn_,
|
||||
SelectTrigger_Shadcn_,
|
||||
SelectValue_Shadcn_,
|
||||
} from 'ui'
|
||||
|
||||
import UpgradePlan from './UpgradePlan'
|
||||
|
||||
const MobileHeader = ({
|
||||
@@ -117,20 +126,24 @@ const PricingComparisonTable = ({
|
||||
{/* Free - Mobile */}
|
||||
<div className="bg-background p-2 sticky top-14 z-10 pt-4">
|
||||
<div className="bg-surface-100 rounded-lg border py-2 px-4 flex justify-between items-center">
|
||||
<label className="text-foreground-lighter">Change plan</label>
|
||||
<Select
|
||||
id="change-plan"
|
||||
<label className="text-foreground-lighter grow">Change plan</label>
|
||||
<Select_Shadcn_
|
||||
name="Change plan"
|
||||
layout="vertical"
|
||||
value={activeMobilePlan}
|
||||
className="min-w-[120px]"
|
||||
onChange={(e) => setActiveMobilePlan(e.target.value)}
|
||||
onValueChange={(value) => setActiveMobilePlan(value)}
|
||||
>
|
||||
<Select.Option value="Free">Free</Select.Option>
|
||||
<Select.Option value="Pro">Pro</Select.Option>
|
||||
<Select.Option value="Team">Team</Select.Option>
|
||||
<Select.Option value="Enterprise">Enterprise</Select.Option>
|
||||
</Select>
|
||||
<SelectTrigger_Shadcn_ id="change-plan" className="w-auto min-w-[120px]">
|
||||
<SelectValue_Shadcn_ />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectGroup_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="Free">Free</SelectItem_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="Pro">Pro</SelectItem_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="Team">Team</SelectItem_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="Enterprise">Enterprise</SelectItem_Shadcn_>
|
||||
</SelectGroup_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</div>
|
||||
</div>
|
||||
{activeMobilePlan === 'Free' && (
|
||||
|
||||
@@ -244,7 +244,8 @@ testRunner('table editor', () => {
|
||||
// insert row with enum value
|
||||
await page.getByTestId('table-editor-insert-new-row').click()
|
||||
await page.getByText('Insert a new row into').click()
|
||||
await page.getByRole('combobox').selectOption('value1')
|
||||
await page.getByRole('combobox').click()
|
||||
await page.getByRole('option', { name: 'value1' }).click()
|
||||
await page.getByTestId('action-bar-save-row').click()
|
||||
await expect(page.getByTestId('side-panel-row-editor')).not.toBeVisible()
|
||||
await expect(page.getByRole('gridcell', { name: 'value1' })).toBeVisible()
|
||||
@@ -252,7 +253,8 @@ testRunner('table editor', () => {
|
||||
// insert row with another enum value
|
||||
await page.getByTestId('table-editor-insert-new-row').click()
|
||||
await page.getByText('Insert a new row into').click()
|
||||
await page.getByRole('combobox').selectOption('value2')
|
||||
await page.getByRole('combobox').click()
|
||||
await page.getByRole('option', { name: 'value2' }).click()
|
||||
await page.getByTestId('action-bar-save-row').click()
|
||||
await expect(page.getByRole('gridcell', { name: 'value2' })).toBeVisible({ timeout: 10_000 })
|
||||
|
||||
@@ -903,7 +905,8 @@ testRunner('table editor', () => {
|
||||
const updateTrueResponse = waitForApiResponse(page, 'pg-meta', ref, 'query?key=', {
|
||||
method: 'POST',
|
||||
})
|
||||
await booleanEditor.selectOption('true')
|
||||
await booleanEditor.click()
|
||||
await page.getByRole('option', { name: 'true' }).click()
|
||||
await page.getByRole('columnheader', { name: 'id' }).click()
|
||||
await updateTrueResponse
|
||||
|
||||
@@ -922,7 +925,8 @@ testRunner('table editor', () => {
|
||||
const updateFalseResponse = waitForApiResponse(page, 'pg-meta', ref, 'query?key=', {
|
||||
method: 'POST',
|
||||
})
|
||||
await booleanEditor.selectOption('false')
|
||||
await booleanEditor.click()
|
||||
await page.getByRole('option', { name: 'false' }).click()
|
||||
await page.getByRole('columnheader', { name: 'id' }).click()
|
||||
await updateFalseResponse
|
||||
|
||||
@@ -1019,7 +1023,8 @@ testRunner('table editor', () => {
|
||||
const updateNullResponse = waitForApiResponse(page, 'pg-meta', ref, 'query?key=', {
|
||||
method: 'POST',
|
||||
})
|
||||
await booleanEditor.selectOption('null')
|
||||
await booleanEditor.click()
|
||||
await page.getByRole('option', { name: 'null' }).click()
|
||||
await page.getByRole('columnheader', { name: 'id' }).click()
|
||||
await updateNullResponse
|
||||
|
||||
@@ -1034,7 +1039,8 @@ testRunner('table editor', () => {
|
||||
const updateFalseResponse = waitForApiResponse(page, 'pg-meta', ref, 'query?key=', {
|
||||
method: 'POST',
|
||||
})
|
||||
await booleanEditor.selectOption('false')
|
||||
await booleanEditor.click()
|
||||
await page.getByRole('option', { name: 'false' }).click()
|
||||
await page.getByRole('columnheader', { name: 'id' }).click()
|
||||
await updateFalseResponse
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ export * from './src/lib/utils'
|
||||
|
||||
// DATA ENTRY
|
||||
|
||||
export * from './src/components/Select'
|
||||
export * from './src/components/Listbox'
|
||||
export * from './src/components/Input'
|
||||
export * from './src/components/Toggle'
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
.sbui-select-container {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
.sbui-select {
|
||||
@apply block w-full bg-white pl-3 pr-10 py-2 text-sm rounded-md shadow-sm transition-all;
|
||||
|
||||
@apply text-input-value-light border border-solid border-input-border-light;
|
||||
@apply focus:ring-input-border-focus-light focus:border-input-border-focus-light focus:outline-none;
|
||||
|
||||
@apply dark:bg-transparent dark:text-input-value-dark dark:border-input-border-dark;
|
||||
@apply dark:focus:border-input-border-focus-dark dark:focus:ring-input-border-focus-dark;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
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-select option {
|
||||
@apply bg-white text-gray-800;
|
||||
}
|
||||
|
||||
.sbui-select:focus {
|
||||
box-shadow: 0 0 0 2px rgba(62, 207, 142, 0.1);
|
||||
}
|
||||
|
||||
.sbui-select--error {
|
||||
@apply border-red-500;
|
||||
}
|
||||
|
||||
.sbui-select--borderless {
|
||||
@apply border-transparent shadow-none;
|
||||
}
|
||||
|
||||
/*
|
||||
Select sizes
|
||||
*/
|
||||
|
||||
.sbui-select--tiny {
|
||||
@apply px-2.5 py-1.5 text-xs;
|
||||
}
|
||||
.sbui-select--small {
|
||||
@apply px-3 py-2 text-sm leading-4;
|
||||
}
|
||||
.sbui-select--medium {
|
||||
@apply px-4 py-2 text-sm;
|
||||
}
|
||||
.sbui-select--large {
|
||||
@apply px-4 py-2 text-base;
|
||||
}
|
||||
.sbui-select--xlarge {
|
||||
@apply px-6 py-3 text-base;
|
||||
}
|
||||
|
||||
.sbui-select-actions-container {
|
||||
@apply absolute inset-y-0 right-0 pl-3 pr-1 mr-5 flex items-center;
|
||||
}
|
||||
|
||||
/*
|
||||
Select icon
|
||||
*/
|
||||
|
||||
.sbui-select--with-icon {
|
||||
@apply pl-7;
|
||||
}
|
||||
|
||||
/*
|
||||
Select Chevron
|
||||
*/
|
||||
|
||||
.sbui-select-chevron-container {
|
||||
@apply absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none;
|
||||
}
|
||||
|
||||
.sbui-select-chevron {
|
||||
@apply h-5 w-5 text-gray-400;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import Select from './Select'
|
||||
|
||||
describe('#Select', () => {
|
||||
it('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('should have "form-select--error" class', () => {
|
||||
render(
|
||||
<Select error data-testid="form-select">
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
</Select>
|
||||
)
|
||||
expect(screen.queryByTestId('form-select')).toHaveClass(
|
||||
'block box-border w-full rounded-md shadow-sm transition-all text-foreground border focus-visible:shadow-md outline-none focus:ring-current focus:ring-2 focus-visible:border-foreground-muted focus-visible:ring-background-control placeholder-foreground-muted appearance-none bg-none bg-destructive-200 border border-destructive-500 focus:ring-destructive-400 placeholder:text-destructive-400 text-base md:text-sm px-4 py-2'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,164 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import React 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'
|
||||
|
||||
interface OptionProps {
|
||||
value: string
|
||||
children: React.ReactNode
|
||||
selected?: boolean
|
||||
}
|
||||
|
||||
interface OptGroupProps {
|
||||
label: string
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export interface Props extends Omit<React.InputHTMLAttributes<HTMLSelectElement>, 'size'> {
|
||||
autofocus?: boolean
|
||||
children: React.ReactNode
|
||||
descriptionText?: string
|
||||
error?: string
|
||||
icon?: any
|
||||
inputRef?: string
|
||||
label?: string
|
||||
afterLabel?: string
|
||||
beforeLabel?: string
|
||||
labelOptional?: string
|
||||
layout?: 'horizontal' | 'vertical'
|
||||
reveal?: boolean
|
||||
actions?: React.ReactNode
|
||||
size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
|
||||
borderless?: boolean
|
||||
validation?: (x: any) => void
|
||||
}
|
||||
|
||||
export const ColLayout = (props: any) => <div>{props.children}</div>
|
||||
|
||||
/**
|
||||
* @deprecated Use `import { Select_shadcn_ } from "ui"` instead
|
||||
*/
|
||||
function Select({
|
||||
autoComplete,
|
||||
autofocus,
|
||||
children,
|
||||
className,
|
||||
descriptionText,
|
||||
disabled,
|
||||
error,
|
||||
icon,
|
||||
id = '',
|
||||
inputRef,
|
||||
label,
|
||||
afterLabel,
|
||||
beforeLabel,
|
||||
labelOptional,
|
||||
layout,
|
||||
name = '',
|
||||
placeholder,
|
||||
required,
|
||||
value = undefined,
|
||||
defaultValue = undefined,
|
||||
style,
|
||||
size = 'medium',
|
||||
borderless = false,
|
||||
validation,
|
||||
...props
|
||||
}: Props) {
|
||||
const __styles = styleHandler('select')
|
||||
|
||||
let classesContainer = [__styles.container]
|
||||
if (className) classesContainer.push(className)
|
||||
|
||||
let classes = [__styles.base]
|
||||
if (error) classes.push(__styles.variants.error)
|
||||
if (!error) classes.push(__styles.variants.standard)
|
||||
if (icon) classes.push(__styles.with_icon[size])
|
||||
if (size) classes.push(__styles.size[size])
|
||||
if (disabled) classes.push(__styles.disabled)
|
||||
|
||||
return (
|
||||
<FormLayout
|
||||
label={label}
|
||||
afterLabel={afterLabel}
|
||||
beforeLabel={beforeLabel}
|
||||
labelOptional={labelOptional}
|
||||
layout={layout}
|
||||
id={id}
|
||||
error={error}
|
||||
descriptionText={descriptionText}
|
||||
className={className}
|
||||
style={style}
|
||||
size={size}
|
||||
>
|
||||
<div className={__styles.container}>
|
||||
<select
|
||||
id={id}
|
||||
name={name}
|
||||
data-size={size}
|
||||
defaultValue={defaultValue}
|
||||
autoComplete={autoComplete}
|
||||
autoFocus={autofocus}
|
||||
className={classes.join(' ')}
|
||||
ref={inputRef}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
// @ts-ignore
|
||||
placeholder={placeholder}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</select>
|
||||
{icon && <InputIconContainer size={size} icon={icon} />}
|
||||
{error && (
|
||||
<div className={__styles.actions_container}>
|
||||
{error && <InputErrorIcon size={size} />}
|
||||
</div>
|
||||
)}
|
||||
<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>
|
||||
</div>
|
||||
</FormLayout>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use ./SelectItem_Shadcn_ instead
|
||||
*/
|
||||
export function Option({ value, children, selected }: OptionProps) {
|
||||
return (
|
||||
<option value={value} selected={selected}>
|
||||
{children}
|
||||
</option>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use ./SelectGroup_Shadcn_ instead
|
||||
*/
|
||||
export function OptGroup({ label, children }: OptGroupProps) {
|
||||
return <optgroup label={label}>{children}</optgroup>
|
||||
}
|
||||
|
||||
Select.Option = Option
|
||||
Select.OptGroup = OptGroup
|
||||
|
||||
export default Select
|
||||
@@ -1,4 +0,0 @@
|
||||
import Select from './Select'
|
||||
|
||||
export default Select
|
||||
export { default as Select } from './Select'
|
||||
@@ -458,52 +458,6 @@ export default {
|
||||
textarea_actions_container_items: 'flex items-center',
|
||||
},
|
||||
|
||||
/*
|
||||
* Select
|
||||
*/
|
||||
|
||||
select: {
|
||||
base: `
|
||||
block
|
||||
box-border
|
||||
w-full
|
||||
rounded-md
|
||||
shadow-sm
|
||||
transition-all
|
||||
text-foreground
|
||||
border
|
||||
focus-visible:shadow-md
|
||||
${defaults.focus}
|
||||
focus-visible:border-foreground-muted
|
||||
focus-visible:ring-background-control
|
||||
${defaults.placeholder}
|
||||
|
||||
appearance-none
|
||||
bg-none
|
||||
`,
|
||||
variants: {
|
||||
standard: `
|
||||
bg-background
|
||||
border border-strong
|
||||
`,
|
||||
error: `
|
||||
bg-destructive-200
|
||||
border border-destructive-500
|
||||
focus:ring-destructive-400
|
||||
placeholder:text-destructive-400
|
||||
`,
|
||||
},
|
||||
container: 'relative',
|
||||
with_icon: with_icon_spacing_sizes,
|
||||
size: {
|
||||
...default__padding_and_text,
|
||||
},
|
||||
disabled: 'opacity-50',
|
||||
actions_container: 'absolute inset-y-0 right-0 pl-3 pr-1 mr-5 flex items-center',
|
||||
chevron_container: 'absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none',
|
||||
chevron: 'h-5 w-5 text-foreground-lighter',
|
||||
},
|
||||
|
||||
sidepanel: {
|
||||
base: `
|
||||
z-50
|
||||
|
||||
Reference in New Issue
Block a user