-
-
Install Qovery Operator
-
+
+ Install Qovery Operator
{isBootstrapLoading ? (
@@ -447,7 +453,7 @@ function InstallStep({
contact support
-
+
)
}
diff --git a/libs/domains/clusters/feature/src/lib/platform-configuration/platform-configuration-utils.spec.ts b/libs/domains/clusters/feature/src/lib/utils/platform-configuration-utils.spec.ts
similarity index 100%
rename from libs/domains/clusters/feature/src/lib/platform-configuration/platform-configuration-utils.spec.ts
rename to libs/domains/clusters/feature/src/lib/utils/platform-configuration-utils.spec.ts
diff --git a/libs/domains/clusters/feature/src/lib/platform-configuration/platform-configuration-utils.ts b/libs/domains/clusters/feature/src/lib/utils/platform-configuration-utils.ts
similarity index 95%
rename from libs/domains/clusters/feature/src/lib/platform-configuration/platform-configuration-utils.ts
rename to libs/domains/clusters/feature/src/lib/utils/platform-configuration-utils.ts
index de29d664f80..e1bdba05a8f 100644
--- a/libs/domains/clusters/feature/src/lib/platform-configuration/platform-configuration-utils.ts
+++ b/libs/domains/clusters/feature/src/lib/utils/platform-configuration-utils.ts
@@ -12,7 +12,7 @@ import {
type PlatformTemplateSummaryResponse,
} from 'qovery-typescript-axios'
import { match } from 'ts-pattern'
-import { type CatalogVariableField, type CatalogVariableValue, getCatalogVariableValue } from '@qovery/shared/util-js'
+import { type SchemaField, type SchemaFieldValue, getSchemaFieldValue } from '@qovery/shared/schema-fields/feature'
export function toPlatformCloudVendor(
cloudProvider: CloudProviderEnum | CloudVendorEnum | undefined
@@ -39,6 +39,7 @@ export type PlatformFieldDescriptor = {
description?: string | null
sensitive: boolean
required: boolean
+ readOnly?: boolean
constraints: FieldSchemaConstraintsResponse
defaultValue?: string | null
}
@@ -159,7 +160,7 @@ export function getCurrentPlatformConfigurationPreview(
return preview
}
-export function toCatalogVariableField(field: PlatformFieldDescriptor): CatalogVariableField {
+export function toSchemaField(field: PlatformFieldDescriptor): SchemaField {
return {
key: field.key,
label: field.label,
@@ -183,7 +184,7 @@ export function applyPlatformConfigurationDefaults(
): Record
{
const defaultValues = Object.fromEntries(
fields.filter(isPlatformScalarField).flatMap((field) => {
- const defaultValue = getCatalogVariableValue(field, undefined)
+ const defaultValue = getSchemaFieldValue(field, undefined)
if (defaultValue === undefined) return []
return [[field.key, toPlatformConfigurationValue(field, defaultValue)]]
@@ -193,10 +194,7 @@ export function applyPlatformConfigurationDefaults(
return { ...defaultValues, ...values }
}
-export function toPlatformConfigurationValue(
- field: Pick,
- value: CatalogVariableValue
-) {
+export function toPlatformConfigurationValue(field: Pick, value: SchemaFieldValue) {
if (field.type !== 'number') return value
if (typeof value !== 'string') return undefined
// Keep the empty string: it marks a field the user explicitly cleared, so
diff --git a/libs/domains/services/feature/src/lib/service-overview/instance-metrics/__snapshots__/instance-metrics.spec.tsx.snap b/libs/domains/services/feature/src/lib/service-overview/instance-metrics/__snapshots__/instance-metrics.spec.tsx.snap
index f2d2a5a167c..c54253f5f9e 100644
--- a/libs/domains/services/feature/src/lib/service-overview/instance-metrics/__snapshots__/instance-metrics.spec.tsx.snap
+++ b/libs/domains/services/feature/src/lib/service-overview/instance-metrics/__snapshots__/instance-metrics.spec.tsx.snap
@@ -226,7 +226,7 @@ exports[`InstanceMetrics should match snapshot with data 1`] = `
exports[`InstanceMetrics should match snapshot with empty data 1`] = `
{
+describe('SchemaFieldInput', () => {
beforeEach(() => {
jest.useFakeTimers()
})
@@ -23,7 +23,7 @@ describe('CatalogVariableInput', () => {
})
it('renders a number field with its validation error in place of the description', () => {
- renderWithProviders()
+ renderWithProviders()
expect(screen.getByRole('spinbutton', { name: 'Retention' })).toHaveValue(12)
expect(screen.getByText('Retention is invalid.')).toBeInTheDocument()
@@ -31,7 +31,7 @@ describe('CatalogVariableInput', () => {
})
it('renders row layout with its description beside the control', () => {
- renderWithProviders()
+ renderWithProviders()
expect(screen.getByRole('spinbutton', { name: 'Retention' })).toHaveValue(12)
expect(screen.getByText('Retention period in weeks.')).toBeInTheDocument()
@@ -43,7 +43,7 @@ describe('CatalogVariableInput', () => {
"CPU/memory budget applied to the active Loki workloads. CHART_DEFAULT keeps the chart's own behavior, while presets apply Qovery's versioned budgets. Budgets are not capacity guarantees. Every resource field may be changed at any time; an update recompiles the Helm values and rolls the workloads. Preset budgets apply versioned resource values."
const { userEvent } = renderWithProviders(
- {
})
it('shows the whole row description while searching', () => {
- renderWithProviders()
+ renderWithProviders()
const match = screen.getByText('weeks', { selector: 'mark' })
expect(match.closest('p')).not.toHaveClass('line-clamp-3')
@@ -70,7 +70,7 @@ describe('CatalogVariableInput', () => {
it('names the row checkbox after its field', async () => {
const onChange = jest.fn()
const { userEvent } = renderWithProviders(
- {
const error = 'Loki high availability requires object storage.'
renderWithProviders(
- {
['a checkbox in the card layout', { type: 'bool' }, 'card', 'checkbox', 'checkbox'],
] as const)('ties the error to %s', (_, fieldType, layout, role, booleanControl) => {
renderWithProviders(
- {
const [value, setValue] = useState('large')
return (
<>
-
+
@@ -150,13 +150,8 @@ describe('CatalogVariableInput', () => {
const field = { key: 'enabled', label: 'Enabled', type: 'bool' } as const
renderWithProviders(
<>
-
-
+
+
>
)
@@ -166,7 +161,7 @@ describe('CatalogVariableInput', () => {
it('does not flag a control without error', () => {
renderWithProviders(
- {
it('renders sensitive values as passwords', () => {
renderWithProviders(
- {
it.each(['card', 'row'] as const)('masks sensitive number values in the %s layout', (layout) => {
renderWithProviders(
- {
it('does not let a disabled sensitive field reveal its value', () => {
renderWithProviders(
- {
it('keeps the toggle presentation as the default for boolean fields', async () => {
const onChange = jest.fn()
const { userEvent } = renderWithProviders(
- {
it('renders a boolean control even when the field declares allowed values', async () => {
const onChange = jest.fn()
const { userEvent } = renderWithProviders(
- {
it('supports a checkbox presentation for boolean fields', async () => {
const onChange = jest.fn()
const { userEvent } = renderWithProviders(
- void
- value: CatalogVariableValue | undefined
+ onChange: (value: SchemaFieldValue) => void
+ value: SchemaFieldValue | undefined
}
const DESCRIPTION_TRUNCATE_LIMIT = 200
const formatDescription = (description: string) => description.replace(/([.!?])\s+/g, '$1\n')
-export function CatalogVariableDescription({ description, highlight }: { description: string; highlight?: string }) {
+export function SchemaFieldDescription({ description, highlight }: { description: string; highlight?: string }) {
const formattedDescription = formatDescription(description)
return (
@@ -47,9 +47,9 @@ export function CatalogVariableDescription({ description, highlight }: { descrip
)
}
-type CatalogVariableControlProps = Omit & { errorId: string }
+type SchemaFieldControlProps = Omit & { errorId: string }
-function CatalogVariableControl({
+function SchemaFieldControl({
autoFocus,
booleanControl = 'toggle',
disabled,
@@ -58,7 +58,7 @@ function CatalogVariableControl({
field,
onChange,
value,
-}: CatalogVariableControlProps) {
+}: SchemaFieldControlProps) {
const errorProps = {
'aria-describedby': error ? errorId : undefined,
'aria-invalid': error ? true : undefined,
@@ -136,7 +136,7 @@ function CatalogVariableControl({
)
}
-function CatalogVariableInputRow({
+function SchemaFieldInputRow({
autoFocus,
booleanControl,
disabled,
@@ -146,19 +146,17 @@ function CatalogVariableInputRow({
highlight,
onChange,
value,
-}: CatalogVariableControlProps) {
+}: SchemaFieldControlProps) {
return (
- {field.description ? (
-
- ) : null}
+ {field.description ?
: null}
-
{
+ it('formats snake_case and kebab-case keys into a readable label', () => {
+ expect(formatSchemaFieldKey('database_name')).toBe('Database name')
+ expect(formatSchemaFieldKey('log-storage')).toBe('Log storage')
+ expect(formatSchemaFieldKey('CUSTOMER_MANAGED')).toBe('CUSTOMER MANAGED')
+ })
+})
+
+describe('schema field value coercion', () => {
+ it('returns string and boolean values with safe fallbacks', () => {
+ expect(getSchemaFieldStringValue('value')).toBe('value')
+ expect(getSchemaFieldStringValue(true)).toBe('')
+ expect(getSchemaFieldStringValue(undefined)).toBe('')
+ expect(getSchemaFieldBooleanValue(true)).toBe(true)
+ expect(getSchemaFieldBooleanValue('value')).toBe(false)
+ expect(getSchemaFieldBooleanValue(undefined)).toBe(false)
+ })
+
+ it('resolves values against the field default without losing their type', () => {
+ expect(getSchemaFieldValue({ type: 'number', defaultValue: '12' }, undefined)).toBe('12')
+ expect(getSchemaFieldValue({ type: 'number', defaultValue: '12' }, 24)).toBe('24')
+ expect(getSchemaFieldValue({ type: 'bool', defaultValue: 'true' }, undefined)).toBe(true)
+ expect(getSchemaFieldValue({ type: 'bool' }, 'false')).toBe(false)
+ expect(getSchemaFieldValue({ type: 'bool' }, undefined)).toBeUndefined()
+ expect(getSchemaFieldValue({ type: 'string', defaultValue: null }, undefined)).toBeUndefined()
+ expect(getSchemaFieldValue({ type: 'string' }, { unexpected: true })).toBeUndefined()
+ })
+})
+
+describe('schema field validation', () => {
+ it('treats booleans and non-blank strings as fulfilled', () => {
+ expect(isSchemaFieldValueFulfilled(false)).toBe(true)
+ expect(isSchemaFieldValueFulfilled('value')).toBe(true)
+ expect(isSchemaFieldValueFulfilled(' ')).toBe(false)
+ expect(isSchemaFieldValueFulfilled(undefined)).toBe(false)
+ })
+
+ it('matches values against the field pattern and ignores broken patterns', () => {
+ expect(isSchemaFieldValueMatchingPattern({ pattern: '^[a-z]+$' }, 'value')).toBe(true)
+ expect(isSchemaFieldValueMatchingPattern({ pattern: '^[a-z]+$' }, 'Value')).toBe(false)
+ expect(isSchemaFieldValueMatchingPattern({ pattern: '(' }, 'value')).toBe(true)
+ expect(isSchemaFieldValueMatchingPattern({}, 'value')).toBe(true)
+ expect(isSchemaFieldValueMatchingPattern({ pattern: '^[a-z]+$' }, undefined)).toBe(true)
+ })
+
+ it('reports length violations with a dedicated message', () => {
+ expect(getSchemaFieldLengthValidationError({ minLength: 2, maxLength: 4 }, 'value')).toBe(
+ 'Value must be between 2 and 4 characters.'
+ )
+ expect(getSchemaFieldLengthValidationError({ minLength: 8 }, 'value')).toBe('Value must be at least 8 characters.')
+ expect(getSchemaFieldLengthValidationError({ maxLength: 3 }, 'value')).toBe('Value must be at most 3 characters.')
+ expect(getSchemaFieldLengthValidationError({ minLength: 2, maxLength: 8 }, 'value')).toBeUndefined()
+ expect(getSchemaFieldLengthValidationError({ minLength: 2 }, undefined)).toBeUndefined()
+ })
+
+ it('reports numeric violations with a dedicated message', () => {
+ expect(getSchemaFieldNumberValidationError({ type: 'number', min: 20, max: 65536 }, '19')).toBe(
+ 'Value must be between 20 and 65536.'
+ )
+ expect(getSchemaFieldNumberValidationError({ type: 'number', min: 20 }, '19')).toBe('Value must be at least 20.')
+ expect(getSchemaFieldNumberValidationError({ type: 'number', max: 65536 }, '65537')).toBe(
+ 'Value must be at most 65536.'
+ )
+ expect(getSchemaFieldNumberValidationError({ type: 'number' }, 'not-a-number')).toBe('Value must be a number.')
+ expect(getSchemaFieldNumberValidationError({ type: 'number', min: 20, max: 65536 }, '20')).toBeUndefined()
+ expect(getSchemaFieldNumberValidationError({ type: 'string', min: 20 }, '19')).toBeUndefined()
+ })
+
+ it('combines length and pattern checks into one validation error', () => {
+ expect(getSchemaFieldValidationError({ type: 'string', maxLength: 3, pattern: '^[a-z]+$' }, 'value')).toBe(
+ 'Value must be at most 3 characters.'
+ )
+ expect(getSchemaFieldValidationError({ type: 'string', pattern: '^[a-z]+$' }, 'Value')).toBe(
+ 'Value does not match the expected format.'
+ )
+ expect(getSchemaFieldValidationError({ type: 'string', pattern: '^[a-z]+$' }, 'value')).toBeUndefined()
+ })
+
+ it('rejects values outside the allowed values', () => {
+ const field = { type: 'string' as const, allowedValues: ['pvc', 's3'] }
+
+ expect(getSchemaFieldValidationError(field, 'gcs')).toBe('Please select one of the allowed values.')
+ expect(getSchemaFieldValidationError(field, 's3')).toBeUndefined()
+ expect(getSchemaFieldValidationError(field, '')).toBeUndefined()
+ expect(getSchemaFieldValidationError({ type: 'bool', allowedValues: ['true'] }, false)).toBeUndefined()
+ })
+
+ it('validates required state and constraints together', () => {
+ expect(isSchemaFieldValid({ type: 'string', required: true }, undefined)).toBe(false)
+ expect(isSchemaFieldValid({ type: 'string', required: true }, 'value')).toBe(true)
+ expect(isSchemaFieldValid({ type: 'string', required: false, pattern: '^[a-z]+$' }, 'Value')).toBe(false)
+ expect(isSchemaFieldValid({ type: 'string', required: false }, undefined)).toBe(true)
+ })
+})
+
+describe('getSchemaFieldSummaryValue', () => {
+ it('renders booleans as enabled state and masks sensitive values', () => {
+ expect(getSchemaFieldSummaryValue({}, true)).toBe('Enabled')
+ expect(getSchemaFieldSummaryValue({}, false)).toBe('Disabled')
+ expect(getSchemaFieldSummaryValue({ sensitive: true }, 'secret')).toBe('••••••••')
+ expect(getSchemaFieldSummaryValue({ sensitive: true }, '')).toBe('')
+ expect(getSchemaFieldSummaryValue({ sensitive: false }, 'value')).toBe('value')
+ })
+})
diff --git a/libs/shared/util-js/src/lib/catalog-variable-field.ts b/libs/shared/schema-fields/feature/src/lib/schema-field/schema-field.ts
similarity index 61%
rename from libs/shared/util-js/src/lib/catalog-variable-field.ts
rename to libs/shared/schema-fields/feature/src/lib/schema-field/schema-field.ts
index 4abb1c27735..b631846e932 100644
--- a/libs/shared/util-js/src/lib/catalog-variable-field.ts
+++ b/libs/shared/schema-fields/feature/src/lib/schema-field/schema-field.ts
@@ -1,11 +1,11 @@
-export type CatalogVariableValue = string | boolean
+export type SchemaFieldValue = string | boolean
-export type CatalogVariableFieldType = 'string' | 'number' | 'bool'
+export type SchemaFieldType = 'string' | 'number' | 'bool'
-export interface CatalogVariableField {
+export interface SchemaField {
key: string
label: string
- type: CatalogVariableFieldType
+ type: SchemaFieldType
description?: string
required?: boolean
sensitive?: boolean
@@ -18,23 +18,23 @@ export interface CatalogVariableField {
max?: number
}
-export function formatCatalogKey(key: string) {
+export function formatSchemaFieldKey(key: string) {
const label = key.replace(/[-_]/g, ' ')
return `${label.charAt(0).toUpperCase()}${label.slice(1)}`
}
-export function getCatalogStringValue(value: CatalogVariableValue | undefined) {
+export function getSchemaFieldStringValue(value: SchemaFieldValue | undefined) {
return typeof value === 'string' ? value : ''
}
-export function getCatalogBooleanValue(value: CatalogVariableValue | undefined) {
+export function getSchemaFieldBooleanValue(value: SchemaFieldValue | undefined) {
return typeof value === 'boolean' ? value : false
}
-export function getCatalogVariableValue(
- field: Pick & { defaultValue?: string | null },
+export function getSchemaFieldValue(
+ field: Pick & { defaultValue?: string | null },
value: unknown
-): CatalogVariableValue | undefined {
+): SchemaFieldValue | undefined {
const resolvedValue = value ?? field.defaultValue
if (field.type === 'bool') {
if (typeof resolvedValue === 'boolean') return resolvedValue
@@ -46,14 +46,14 @@ export function getCatalogVariableValue(
return undefined
}
-export function isCatalogFieldValueFulfilled(value: CatalogVariableValue | undefined) {
+export function isSchemaFieldValueFulfilled(value: SchemaFieldValue | undefined) {
if (typeof value === 'boolean') return true
return Boolean(value?.trim())
}
-export function isCatalogFieldValueMatchingPattern(
- field: Pick,
- value: CatalogVariableValue | undefined
+export function isSchemaFieldValueMatchingPattern(
+ field: Pick,
+ value: SchemaFieldValue | undefined
) {
if (typeof value !== 'string' || !value || !field.pattern) return true
@@ -64,9 +64,9 @@ export function isCatalogFieldValueMatchingPattern(
}
}
-export function getCatalogFieldLengthValidationError(
- field: Pick,
- value: CatalogVariableValue | undefined
+export function getSchemaFieldLengthValidationError(
+ field: Pick,
+ value: SchemaFieldValue | undefined
) {
if (typeof value !== 'string' || !value) return undefined
@@ -84,9 +84,9 @@ export function getCatalogFieldLengthValidationError(
return undefined
}
-export function getCatalogFieldNumberValidationError(
- field: Pick,
- value: CatalogVariableValue | undefined
+export function getSchemaFieldNumberValidationError(
+ field: Pick,
+ value: SchemaFieldValue | undefined
) {
if (field.type !== 'number' || typeof value !== 'string' || !value.trim()) return undefined
@@ -107,9 +107,9 @@ export function getCatalogFieldNumberValidationError(
return undefined
}
-export function getCatalogFieldValidationError(
- field: Pick,
- value: CatalogVariableValue | undefined
+export function getSchemaFieldValidationError(
+ field: Pick,
+ value: SchemaFieldValue | undefined
) {
if (
field.type !== 'bool' &&
@@ -121,31 +121,28 @@ export function getCatalogFieldValidationError(
return 'Please select one of the allowed values.'
}
- const numberValidationError = getCatalogFieldNumberValidationError(field, value)
+ const numberValidationError = getSchemaFieldNumberValidationError(field, value)
if (numberValidationError) return numberValidationError
- const lengthValidationError = getCatalogFieldLengthValidationError(field, value)
+ const lengthValidationError = getSchemaFieldLengthValidationError(field, value)
if (lengthValidationError) return lengthValidationError
- if (!isCatalogFieldValueMatchingPattern(field, value)) return 'Value does not match the expected format.'
+ if (!isSchemaFieldValueMatchingPattern(field, value)) return 'Value does not match the expected format.'
return undefined
}
-export function isCatalogFieldValid(
+export function isSchemaFieldValid(
field: Pick<
- CatalogVariableField,
+ SchemaField,
'required' | 'type' | 'allowedValues' | 'pattern' | 'minLength' | 'maxLength' | 'min' | 'max'
>,
- value: CatalogVariableValue | undefined
+ value: SchemaFieldValue | undefined
) {
- if (field.required && !isCatalogFieldValueFulfilled(value)) return false
- return !getCatalogFieldValidationError(field, value)
+ if (field.required && !isSchemaFieldValueFulfilled(value)) return false
+ return !getSchemaFieldValidationError(field, value)
}
-export function getCatalogSummaryFieldValue(
- field: Pick,
- value: CatalogVariableValue | undefined
-) {
+export function getSchemaFieldSummaryValue(field: Pick, value: SchemaFieldValue | undefined) {
if (typeof value === 'boolean') return value ? 'Enabled' : 'Disabled'
if (field.sensitive && value) return '••••••••'
return value
diff --git a/libs/shared/schema-fields/feature/tsconfig.json b/libs/shared/schema-fields/feature/tsconfig.json
new file mode 100644
index 00000000000..c88d07daddd
--- /dev/null
+++ b/libs/shared/schema-fields/feature/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "allowJs": false,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true
+ },
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.lib.json"
+ },
+ {
+ "path": "./tsconfig.spec.json"
+ }
+ ],
+ "extends": "../../../../tsconfig.base.json"
+}
diff --git a/libs/shared/schema-fields/feature/tsconfig.lib.json b/libs/shared/schema-fields/feature/tsconfig.lib.json
new file mode 100644
index 00000000000..45b22973977
--- /dev/null
+++ b/libs/shared/schema-fields/feature/tsconfig.lib.json
@@ -0,0 +1,19 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../../dist/out-tsc",
+ "types": ["node", "@nx/react/typings/cssmodule.d.ts", "@nx/react/typings/image.d.ts"]
+ },
+ "exclude": [
+ "jest.config.ts",
+ "src/**/*.spec.ts",
+ "src/**/*.test.ts",
+ "src/**/*.spec.tsx",
+ "src/**/*.test.tsx",
+ "src/**/*.spec.js",
+ "src/**/*.test.js",
+ "src/**/*.spec.jsx",
+ "src/**/*.test.jsx"
+ ],
+ "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
+}
diff --git a/libs/shared/schema-fields/feature/tsconfig.spec.json b/libs/shared/schema-fields/feature/tsconfig.spec.json
new file mode 100644
index 00000000000..1033686367b
--- /dev/null
+++ b/libs/shared/schema-fields/feature/tsconfig.spec.json
@@ -0,0 +1,20 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../../dist/out-tsc",
+ "module": "commonjs",
+ "types": ["jest", "node"]
+ },
+ "include": [
+ "jest.config.ts",
+ "src/**/*.test.ts",
+ "src/**/*.spec.ts",
+ "src/**/*.test.tsx",
+ "src/**/*.spec.tsx",
+ "src/**/*.test.js",
+ "src/**/*.spec.js",
+ "src/**/*.test.jsx",
+ "src/**/*.spec.jsx",
+ "src/**/*.d.ts"
+ ]
+}
diff --git a/libs/shared/ui/src/lib/components/empty-state/__snapshots__/empty-state.spec.tsx.snap b/libs/shared/ui/src/lib/components/empty-state/__snapshots__/empty-state.spec.tsx.snap
index 58cb84cdff6..405dcf3d70c 100644
--- a/libs/shared/ui/src/lib/components/empty-state/__snapshots__/empty-state.spec.tsx.snap
+++ b/libs/shared/ui/src/lib/components/empty-state/__snapshots__/empty-state.spec.tsx.snap
@@ -4,7 +4,7 @@ exports[`EmptyState should match snapshot 1`] = `
{
title: ReactNode
icon?: IconName | ReactNode
iconStyle?: IconStyle
description?: ReactNode
- size?: 'sm' | 'base'
- variant?: 'neutral' | 'positive'
className?: string
}
@@ -23,9 +63,6 @@ export function EmptyState({
variant = 'neutral',
children,
}: EmptyStateProps) {
- const isPositive = variant === 'positive'
- const iconColorClass = isPositive ? 'text-positive' : 'text-neutral-disabled'
-
const emptyStateIcon =
icon &&
(typeof icon === 'string' ? (
@@ -33,7 +70,7 @@ export function EmptyState({