From c0c475246d6b12139d3f8264421ad3537346942f Mon Sep 17 00:00:00 2001 From: Liad Yosef Date: Fri, 13 Dec 2024 20:25:27 +0200 Subject: [PATCH] feat(editor): show correct responsive value according to breakpoint (#6723) This PR adds the ability to display the correct value in the controller according to the Scene size. For example - if the element has `className='pt-[20px] lg:pt-[150px] md:pt-[110px]`, and the Scene has a width of `1000px` (which is larger than `md` but smaller than `lg`) - the `paddingTop` control will correctly show `110px`. **Details:** - This PR augments `ParsedCSSStyleProperty`, so instead of containing just the `value` it now holds: - a list of `variants` (the possible values and for each one the `modifier`s that when applied, the value is chosen. a screen size is a modifier) - the `currentVariant` - which is the value that is currently selected according to the Scene size. - After parsing the Tailwind classes using the 3rd-party parser (as before), we call `getModifiers`, that converts the Tailwind specific variants from the parser representation that looks like: ```ts {type: 'media', value: 'sm'} ``` to our generic representation: ```ts { type: 'media-size', size: { min: { value: 0, unit: 'px' }, max: { value: 100, unit: 'em' } } } ``` - The `getModifiers` function uses an interal `screensConfigToScreenSizes` function - that parses the Tailwind config to have a map of `` to `ScreenSize` - Both are covered in tests in `tailwind-responsive-utils.spec.ts` - This PR uses the utils that were merged in #6716 (specifically `selectValueByBreakpoint` to select the best matching variant) **Example (note that this PR *does not* contain the Scene resize buttons):** **Manual Tests:** I hereby swear that: - [X] I opened a hydrogen project and it loaded - [X] I could navigate to various routes in Play mode --- editor/src/components/canvas/canvas-types.ts | 44 ++- .../canvas/commands/utils/property-utils.ts | 4 +- .../plugins/inline-style-plugin.spec.ts | 14 +- .../canvas/plugins/inline-style-plugin.ts | 4 +- .../tailwind-responsive-utils.spec.ts | 263 ++++++++++++++++++ .../tailwind-responsive-utils.ts | 75 +++++ .../canvas/plugins/tailwind-style-plugin.ts | 98 ++++++- .../canvas/responsive-utils.spec.ts | 12 +- .../src/components/canvas/responsive-utils.ts | 2 +- 9 files changed, 480 insertions(+), 36 deletions(-) create mode 100644 editor/src/components/canvas/plugins/tailwind-style-plugin-utils/tailwind-responsive-utils.spec.ts create mode 100644 editor/src/components/canvas/plugins/tailwind-style-plugin-utils/tailwind-responsive-utils.ts diff --git a/editor/src/components/canvas/canvas-types.ts b/editor/src/components/canvas/canvas-types.ts index 787c04380f36..28ecb7a78734 100644 --- a/editor/src/components/canvas/canvas-types.ts +++ b/editor/src/components/canvas/canvas-types.ts @@ -29,6 +29,7 @@ import type { CSSOverflow, CSSPadding, FlexDirection, + ParsedCSSProperties, } from '../inspector/common/css-utils' import type { ScreenSize } from './responsive-types' @@ -553,17 +554,27 @@ interface CSSStylePropertyNotParsable { interface ParsedCSSStyleProperty { type: 'property' - tags: PropertyTag[] propertyValue: JSExpression | PartOfJSXAttributeValue - value: T + currentVariant: CSSVariant + variants?: CSSVariant[] } -type StyleHoverModifier = { type: 'hover' } -export type StyleMediaSizeModifier = { +type StyleModifierMetadata = { type: string; modifierOrigin?: StyleModifierOrigin } +type StyleHoverModifier = StyleModifierMetadata & { type: 'hover' } +export type StyleMediaSizeModifier = StyleModifierMetadata & { type: 'media-size' size: ScreenSize } export type StyleModifier = StyleHoverModifier | StyleMediaSizeModifier +type InlineModifierOrigin = { type: 'inline' } +type TailwindModifierOrigin = { type: 'tailwind'; variant: string } +export type StyleModifierOrigin = InlineModifierOrigin | TailwindModifierOrigin + +export type ParsedVariant = { + parsedValue: NonNullable + originalValue: string | number | undefined + modifiers: StyleModifier[] +} export type CSSStyleProperty = | CSSStylePropertyNotFound @@ -580,16 +591,35 @@ export function cssStylePropertyNotParsable( return { type: 'not-parsable', originalValue: originalValue } } +export type CSSVariant = { + value: T + modifiers: StyleModifier[] +} + +export function cssVariant(value: T, modifiers: StyleModifier[]): CSSVariant { + return { value: value, modifiers: modifiers } +} + export function cssStyleProperty( - value: T, propertyValue: JSExpression | PartOfJSXAttributeValue, + currentVariant: CSSVariant, + variants: CSSVariant[], ): ParsedCSSStyleProperty { - return { type: 'property', tags: [], value: value, propertyValue: propertyValue } + return { + type: 'property', + propertyValue: propertyValue, + currentVariant: currentVariant, + variants: variants, + } +} + +export function screenSizeModifier(size: ScreenSize): StyleMediaSizeModifier { + return { type: 'media-size', size: size } } export function maybePropertyValue(property: CSSStyleProperty): T | null { if (property.type === 'property') { - return property.value + return property.currentVariant.value } return null } diff --git a/editor/src/components/canvas/commands/utils/property-utils.ts b/editor/src/components/canvas/commands/utils/property-utils.ts index 123bba748aae..b0804b3d48bc 100644 --- a/editor/src/components/canvas/commands/utils/property-utils.ts +++ b/editor/src/components/canvas/commands/utils/property-utils.ts @@ -117,8 +117,8 @@ export function getCSSNumberFromStyleInfo( return { type: 'not-found' } } - if (prop.type === 'not-parsable' || !isCSSNumber(prop.value)) { + if (prop.type === 'not-parsable' || !isCSSNumber(prop.currentVariant.value)) { return { type: 'not-css-number' } } - return { type: 'css-number', number: prop.value } + return { type: 'css-number', number: prop.currentVariant.value } } diff --git a/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts b/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts index e0e8029bce6f..3f449080777c 100644 --- a/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts +++ b/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts @@ -43,15 +43,21 @@ export var storyboard = ( const { flexDirection, gap } = styleInfo! expect(flexDirection).toMatchObject({ type: 'property', - tags: [], - value: 'column', + currentVariant: { value: 'column' }, propertyValue: { value: 'column' }, + variants: [{ value: 'column' }], }) expect(gap).toMatchObject({ type: 'property', - tags: [], - value: cssNumber(2, 'rem'), + currentVariant: { + value: cssNumber(2, 'rem'), + }, propertyValue: { value: '2rem' }, + variants: [ + { + value: cssNumber(2, 'rem'), + }, + ], }) }) diff --git a/editor/src/components/canvas/plugins/inline-style-plugin.ts b/editor/src/components/canvas/plugins/inline-style-plugin.ts index d8c71ec114dc..7d3e29bdb21b 100644 --- a/editor/src/components/canvas/plugins/inline-style-plugin.ts +++ b/editor/src/components/canvas/plugins/inline-style-plugin.ts @@ -13,6 +13,7 @@ import { cssStyleProperty, cssStylePropertyNotParsable, cssStylePropertyNotFound, + cssVariant, } from '../canvas-types' import { mapDropNulls } from '../../../core/shared/array-utils' import { emptyComments, jsExpressionValue } from '../../../core/shared/element-template' @@ -45,7 +46,8 @@ function getPropertyFromInstance

{ + it('returns empty array for non-media variants', () => { + const variants = [{ type: 'hover', value: 'hover' }] + const config: Config = { theme: { screens: {} } } as Config + + const result = getModifiers(variants, config) + expect(result).toEqual([]) + }) + + it('handles default screen sizes correctly', () => { + const variants = [{ type: 'media', value: 'md' }] + const config = null // null config should use defaults + + const result = getModifiers(variants, config) + expect(result).toEqual([ + { + type: 'media-size', + size: { + min: { value: 768, unit: 'px' }, + }, + modifierOrigin: { type: 'tailwind', variant: 'md' }, + }, + ]) + }) + + it('handles custom screen sizes from config', () => { + const variants = [{ type: 'media', value: 'custom' }] + const config = { + theme: { + screens: { + custom: '1000px', + }, + }, + } as unknown as Config + + const result = getModifiers(variants, config) + expect(result).toEqual([ + { + type: 'media-size', + size: { + min: { value: 1000, unit: 'px' }, + }, + modifierOrigin: { type: 'tailwind', variant: 'custom' }, + }, + ]) + }) + + it('handles custom screen sizes from config with mixed units', () => { + const variants = [ + { type: 'media', value: 'custom' }, + { type: 'media', value: 'custom2' }, + ] + const config = { + theme: { + screens: { + custom: { min: '10px', max: '20em' }, + custom2: '30vh', + }, + }, + } as unknown as Config + + const result = getModifiers(variants, config) + expect(result).toEqual([ + { + type: 'media-size', + size: { + min: { value: 10, unit: 'px' }, + max: { value: 20, unit: 'em' }, + }, + modifierOrigin: { type: 'tailwind', variant: 'custom' }, + }, + { + type: 'media-size', + size: { + min: { value: 30, unit: 'vh' }, + }, + modifierOrigin: { type: 'tailwind', variant: 'custom2' }, + }, + ]) + }) + + it('handles min-max range screen sizes', () => { + const variants = [{ type: 'media', value: 'tablet' }] + const config = { + theme: { + screens: { + tablet: { min: '768px', max: '1024px' }, + }, + }, + } as unknown as Config + + const result = getModifiers(variants, config) + expect(result).toEqual([ + { + type: 'media-size', + size: { + min: { value: 768, unit: 'px' }, + max: { value: 1024, unit: 'px' }, + }, + modifierOrigin: { type: 'tailwind', variant: 'tablet' }, + }, + ]) + }) + + it('handles extended screen sizes', () => { + const variants = [{ type: 'media', value: 'extra' }] + const config = { + theme: { + screens: { + sm: '640px', + }, + extend: { + screens: { + extra: '1400px', + }, + }, + }, + } as unknown as Config + + const result = getModifiers(variants, config) + expect(result).toEqual([ + { + type: 'media-size', + size: { + min: { value: 1400, unit: 'px' }, + }, + modifierOrigin: { type: 'tailwind', variant: 'extra' }, + }, + ]) + }) + + it('handles multiple media variants', () => { + const variants = [ + { type: 'media', value: 'sm' }, + { type: 'media', value: 'lg' }, + ] + const config = null // use defaults + + const result = getModifiers(variants, config) + expect(result).toEqual([ + { + type: 'media-size', + size: { + min: { value: 640, unit: 'px' }, + }, + modifierOrigin: { type: 'tailwind', variant: 'sm' }, + }, + { + type: 'media-size', + size: { + min: { value: 1024, unit: 'px' }, + }, + modifierOrigin: { type: 'tailwind', variant: 'lg' }, + }, + ]) + }) + + it('filters out invalid screen sizes', () => { + const variants = [ + { type: 'media', value: 'invalid' }, + { type: 'media', value: 'md' }, + ] + const config = null // use defaults + + const result = getModifiers(variants, config) + expect(result).toEqual([ + { + type: 'media-size', + size: { + min: { value: 768, unit: 'px' }, + }, + modifierOrigin: { type: 'tailwind', variant: 'md' }, + }, + ]) + }) +}) + +describe('screensConfigToScreenSizes', () => { + it('returns default screen sizes when config is null', () => { + const result = screensConfigToScreenSizes(null) + expect(result).toEqual({ + sm: { min: { value: 640, unit: 'px' } }, + md: { min: { value: 768, unit: 'px' } }, + lg: { min: { value: 1024, unit: 'px' } }, + xl: { min: { value: 1280, unit: 'px' } }, + '2xl': { min: { value: 1536, unit: 'px' } }, + }) + }) + + it('handles custom screen sizes', () => { + const config = { + theme: { + screens: { + mobile: '400px', + tablet: '800px', + }, + }, + } as unknown as Config + + const result = screensConfigToScreenSizes(config) + expect(result).toEqual({ + mobile: { min: { value: 400, unit: 'px' } }, + tablet: { min: { value: 800, unit: 'px' } }, + }) + }) + + it('handles min-max range screen sizes', () => { + const config = { + theme: { + screens: { + tablet: { min: '768px', max: '1024px' }, + }, + }, + } as unknown as Config + + const result = screensConfigToScreenSizes(config) + expect(result).toEqual({ + tablet: { + min: { value: 768, unit: 'px' }, + max: { value: 1024, unit: 'px' }, + }, + }) + }) + + it('merges extended screen sizes with base config', () => { + const config = { + theme: { + screens: { + sm: '640px', + }, + extend: { + screens: { + custom: '1400px', + }, + }, + }, + } as unknown as Config + + const result = screensConfigToScreenSizes(config) + expect(result).toEqual({ + sm: { min: { value: 640, unit: 'px' } }, + custom: { min: { value: 1400, unit: 'px' } }, + }) + }) + + it('handles empty config objects', () => { + const config = { + theme: {}, + } as unknown as Config + + const result = screensConfigToScreenSizes(config) + expect(result).toEqual({ + sm: { min: { value: 640, unit: 'px' } }, + md: { min: { value: 768, unit: 'px' } }, + lg: { min: { value: 1024, unit: 'px' } }, + xl: { min: { value: 1280, unit: 'px' } }, + '2xl': { min: { value: 1536, unit: 'px' } }, + }) + }) +}) diff --git a/editor/src/components/canvas/plugins/tailwind-style-plugin-utils/tailwind-responsive-utils.ts b/editor/src/components/canvas/plugins/tailwind-style-plugin-utils/tailwind-responsive-utils.ts new file mode 100644 index 000000000000..6782d6fee6a0 --- /dev/null +++ b/editor/src/components/canvas/plugins/tailwind-style-plugin-utils/tailwind-responsive-utils.ts @@ -0,0 +1,75 @@ +import type { Config } from 'tailwindcss/types/config' +import { type StyleMediaSizeModifier, type StyleModifier } from '../../canvas-types' +import type { ScreenSize } from '../../responsive-types' +import { extractScreenSizeFromCss } from '../../responsive-utils' +import { mapDropNulls } from '../../../../core/shared/array-utils' + +export const TAILWIND_DEFAULT_SCREENS = { + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', + '2xl': '1536px', +} +const defaultTailwindConfig = { + theme: { + screens: TAILWIND_DEFAULT_SCREENS, + }, +} as unknown as Config +type TailwindScreen = string | { min: string; max: string } + +export function screensConfigToScreenSizes(config: Config | null): Record { + const tailwindConfig = config ?? defaultTailwindConfig + const screenSizes: Record = { + ...((tailwindConfig.theme?.screens as Record) ?? + TAILWIND_DEFAULT_SCREENS), + ...((tailwindConfig.theme?.extend?.screens as Record) ?? {}), + } + + return Object.fromEntries( + mapDropNulls(([key, size]) => { + const mediaString = + typeof size === 'string' + ? `@media (min-width: ${size})` + : `@media ${[ + size.min != null ? `(min-width: ${size.min})` : '', + size.max != null ? `(max-width: ${size.max})` : '', + ] + .filter((s) => s != '') + .join(' and ')}` + + const screenSize = extractScreenSizeFromCss(mediaString) + if (screenSize == null) { + return null + } + return [key, screenSize] + }, Object.entries(screenSizes)), + ) +} + +/** + * This function gets variants in the form of {type: 'media', value: 'sm'} + * and turns them into modifiers in the form of [{type: 'media-size', size: {min: {value: 0, unit: 'px'}, max: {value: 100, unit: 'em'}}}] + * according to the tailwind config + */ +export function getModifiers( + variants: { type: string; value: string }[], + config: Config | null, +): StyleModifier[] { + const mediaModifiers = variants.filter((v) => v.type === 'media') + const screenSizes = screensConfigToScreenSizes(config) + + return mediaModifiers + .map((mediaModifier) => { + const size = screenSizes[mediaModifier.value] + if (size == null) { + return null + } + return { + type: 'media-size', + size: size, + modifierOrigin: { type: 'tailwind', variant: mediaModifier.value }, + } as StyleMediaSizeModifier + }) + .filter((m): m is StyleMediaSizeModifier => m != null) +} diff --git a/editor/src/components/canvas/plugins/tailwind-style-plugin.ts b/editor/src/components/canvas/plugins/tailwind-style-plugin.ts index e9fadf9ef689..5f378167a27c 100644 --- a/editor/src/components/canvas/plugins/tailwind-style-plugin.ts +++ b/editor/src/components/canvas/plugins/tailwind-style-plugin.ts @@ -1,4 +1,5 @@ import * as TailwindClassParser from '@xengine/tailwindcss-class-parser' +import type { Right } from '../../../core/shared/either' import { defaultEither, flatMapEither, isLeft } from '../../../core/shared/either' import { getClassNameAttribute } from '../../../core/tailwind/tailwind-options' import { getElementFromProjectContents } from '../../editor/store/editor-state' @@ -7,8 +8,8 @@ import { cssParsers } from '../../inspector/common/css-utils' import { mapDropNulls } from '../../../core/shared/array-utils' import type { StylePlugin, StylePluginContext } from './style-plugins' import type { Config } from 'tailwindcss/types/config' -import type { StyleInfo } from '../canvas-types' -import { cssStyleProperty, type CSSStyleProperty } from '../canvas-types' +import type { ParsedVariant, StyleInfo } from '../canvas-types' +import { cssStyleProperty, cssVariant, type CSSStyleProperty } from '../canvas-types' import * as UCL from './tailwind-style-plugin-utils/update-class-list' import { assertNever } from '../../../core/shared/utils' import { @@ -18,22 +19,49 @@ import { import { emptyComments, type JSXAttributes } from 'utopia-shared/src/types' import * as PP from '../../../core/shared/property-path' import { jsExpressionValue } from '../../../core/shared/element-template' -import { getContainingSceneSize } from '../responsive-utils' +import { getContainingSceneSize, selectValueByBreakpoint } from '../responsive-utils' +import { getModifiers } from './tailwind-style-plugin-utils/tailwind-responsive-utils' -const parseTailwindPropertyFactory = +type StyleValueVariants = { + value: string | number | undefined + modifiers?: TailwindModifier[] +}[] + +export const parseTailwindPropertyFactory = (config: Config | null, context: StylePluginContext) => ( - value: string | number | undefined, + styleDefinition: StyleValueVariants | undefined, prop: T, ): CSSStyleProperty> | null => { - const parsed = cssParsers[prop](value, null) - if (isLeft(parsed) || parsed.value == null) { + if (styleDefinition == null) { + return null + } + const possibleVariants: ParsedVariant[] = styleDefinition + .map((v) => ({ + parsedValue: cssParsers[prop](v.value, null), + originalValue: v.value, + modifiers: getModifiers(v.modifiers ?? [], config), + })) + .filter((v) => v.parsedValue != null && !isLeft(v.parsedValue)) + .map((v) => ({ + ...v, + parsedValue: (v.parsedValue as Right).value as NonNullable< + ParsedCSSProperties[T] + >, + })) + const sceneSize = context.sceneSize.type === 'scene' ? context.sceneSize.width : undefined + const selectedVariant = selectValueByBreakpoint(possibleVariants, sceneSize) + if (selectedVariant == null) { return null } - return cssStyleProperty(parsed.value, jsExpressionValue(value, emptyComments)) + return cssStyleProperty( + jsExpressionValue(selectedVariant.originalValue, emptyComments), + cssVariant(selectedVariant.parsedValue, selectedVariant.modifiers), + possibleVariants.map((variant) => cssVariant(variant.parsedValue, variant.modifiers)), + ) } -const TailwindPropertyMapping: Record = { +export const TailwindPropertyMapping: Record = { left: 'positionLeft', right: 'positionRight', top: 'positionTop', @@ -84,19 +112,51 @@ function stringifyPropertyValue(value: string | number): string { } } -function getTailwindClassMapping(classes: string[], config: Config | null): Record { - const mapping: Record = {} +type TailwindParsedStyle = { + kind: string + property: string + value: string + variants?: { type: string; value: string }[] +} + +export function getTailwindClassMapping( + classes: string[], + config: Config | null, +): Record { + const mapping: Record = {} classes.forEach((className) => { - const parsed = TailwindClassParser.parse(className, config ?? undefined) - if (parsed.kind === 'error' || !isSupportedTailwindProperty(parsed.property)) { + const parsed: TailwindParsedStyle | undefined = TailwindClassParser.parse( + className, + config ?? undefined, + ) + if ( + parsed == null || + parsed.kind === 'error' || + !isSupportedTailwindProperty(parsed.property) + ) { return } - mapping[parsed.property] = parsed.value + mapping[parsed.property] = mapping[parsed.property] ?? [] + const modifiers = (parsed.variants ?? []).filter(isTailwindModifier) + mapping[parsed.property].push({ + value: parsed.value, + modifiers: modifiers, + }) }) return mapping } -const underscoresToSpaces = (s: string | undefined) => s?.replace(/[-_]/g, ' ') +const underscoresToSpaces = ( + styleDef: StyleValueVariants | undefined, +): StyleValueVariants | undefined => { + if (styleDef == null) { + return undefined + } + return styleDef.map((style) => ({ + ...style, + value: typeof style.value === 'string' ? style.value.replace(/[-_]/g, ' ') : style.value, + })) +} export const TailwindPlugin = (config: Config | null): StylePlugin => ({ name: 'Tailwind', @@ -222,3 +282,11 @@ export const TailwindPlugin = (config: Config | null): StylePlugin => ({ ) }, }) + +type TailwindModifier = { type: 'media'; value: string } | { type: 'hover'; value: string } +function isTailwindModifier(modifier: { + type: string + value: string +}): modifier is TailwindModifier { + return modifier.type === 'media' || modifier.type === 'hover' +} diff --git a/editor/src/components/canvas/responsive-utils.spec.ts b/editor/src/components/canvas/responsive-utils.spec.ts index dcbcc0c434c1..540a68d815a3 100644 --- a/editor/src/components/canvas/responsive-utils.spec.ts +++ b/editor/src/components/canvas/responsive-utils.spec.ts @@ -2,7 +2,7 @@ import * as csstree from 'css-tree' import { mediaQueryToScreenSize, selectValueByBreakpoint } from './responsive-utils' import type { ScreenSize, MediaQuery } from './responsive-types' import { extractScreenSizeFromCss } from './responsive-utils' -import type { StyleModifier } from './canvas-types' +import type { CSSVariant, StyleModifier } from './canvas-types' describe('extractScreenSizeFromCss', () => { it('extracts screen size from simple media query', () => { @@ -60,7 +60,7 @@ describe('extractScreenSizeFromCss', () => { }) describe('selectValueByBreakpoint', () => { - const variants: { value: string; modifiers?: StyleModifier[] }[] = [ + const variants: CSSVariant[] = [ { value: 'Desktop Value', modifiers: [{ type: 'media-size', size: { min: { value: 200, unit: 'px' } } }], @@ -86,7 +86,7 @@ describe('selectValueByBreakpoint', () => { value: 'Mobile Value', modifiers: [{ type: 'media-size', size: { min: { value: 60, unit: 'px' } } }], }, - { value: 'Default Value' }, + { value: 'Default Value', modifiers: [] }, ] const tests: { title: string; screenSize: number; expected: string }[] = [ { title: 'selects the correct value', screenSize: 150, expected: 'Tablet Value' }, @@ -116,7 +116,7 @@ describe('selectValueByBreakpoint', () => { }) it('selects null if no matching breakpoint and no default value', () => { - const largeVariants: { value: string; modifiers?: StyleModifier[] }[] = [ + const largeVariants: CSSVariant[] = [ { value: 'Desktop Value', modifiers: [{ type: 'media-size', size: { min: { value: 200, unit: 'px' } } }], @@ -129,12 +129,12 @@ describe('selectValueByBreakpoint', () => { expect(selectValueByBreakpoint(largeVariants, 50)).toBeNull() }) it('selects default value if no media modifiers', () => { - const noMediaVariants: { value: string; modifiers?: StyleModifier[] }[] = [ + const noMediaVariants: CSSVariant[] = [ { value: 'Hover Value', modifiers: [{ type: 'hover' }], }, - { value: 'Default Value' }, + { value: 'Default Value', modifiers: [] }, ] expect(selectValueByBreakpoint(noMediaVariants, 50)?.value).toEqual('Default Value') }) diff --git a/editor/src/components/canvas/responsive-utils.ts b/editor/src/components/canvas/responsive-utils.ts index 9d828e6399d5..31edb72310c3 100644 --- a/editor/src/components/canvas/responsive-utils.ts +++ b/editor/src/components/canvas/responsive-utils.ts @@ -154,7 +154,7 @@ function getMediaModifier( )[0] } -export function selectValueByBreakpoint( +export function selectValueByBreakpoint( parsedVariants: T[], sceneWidthInPx?: number, ): T | null {