Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(editor): show correct responsive value according to breakpoint #6723

Merged
merged 7 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 37 additions & 7 deletions editor/src/components/canvas/canvas-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
CSSOverflow,
CSSPadding,
FlexDirection,
ParsedCSSProperties,
} from '../inspector/common/css-utils'
import type { ScreenSize } from './responsive-types'

Expand Down Expand Up @@ -553,17 +554,27 @@ interface CSSStylePropertyNotParsable {

interface ParsedCSSStyleProperty<T> {
type: 'property'
tags: PropertyTag[]
propertyValue: JSExpression | PartOfJSXAttributeValue
value: T
currentVariant: CSSVariant<T>
variants?: CSSVariant<T>[]
}

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<T extends keyof StyleInfo> = {
parsedValue: NonNullable<ParsedCSSProperties[T]>
originalValue: string | number | undefined
modifiers: StyleModifier[]
}

export type CSSStyleProperty<T> =
| CSSStylePropertyNotFound
Expand All @@ -580,16 +591,35 @@ export function cssStylePropertyNotParsable(
return { type: 'not-parsable', originalValue: originalValue }
}

export type CSSVariant<T> = {
value: T
modifiers: StyleModifier[]
}

export function cssVariant<T>(value: T, modifiers: StyleModifier[]): CSSVariant<T> {
return { value: value, modifiers: modifiers }
}

export function cssStyleProperty<T>(
value: T,
propertyValue: JSExpression | PartOfJSXAttributeValue,
currentVariant: CSSVariant<T>,
variants: CSSVariant<T>[],
): ParsedCSSStyleProperty<T> {
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<T>(property: CSSStyleProperty<T>): T | null {
if (property.type === 'property') {
return property.value
return property.currentVariant.value
}
return null
}
Expand Down
4 changes: 2 additions & 2 deletions editor/src/components/canvas/commands/utils/property-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
14 changes: 10 additions & 4 deletions editor/src/components/canvas/plugins/inline-style-plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
},
],
})
})

Expand Down
4 changes: 3 additions & 1 deletion editor/src/components/canvas/plugins/inline-style-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -45,7 +46,8 @@ function getPropertyFromInstance<P extends keyof StyleInfo, T = ParsedCSSPropert
if (Either.isLeft(parsed) || parsed.value == null) {
return cssStylePropertyNotParsable(attribute)
}
return cssStyleProperty(parsed.value, attribute)
const currentVariant = cssVariant(parsed.value, [])
return cssStyleProperty(attribute, currentVariant, [currentVariant])
}

export const InlineStylePlugin: StylePlugin = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
import type { Config } from 'tailwindcss/types/config'
import { getModifiers, screensConfigToScreenSizes } from './tailwind-responsive-utils'

describe('getModifiers', () => {
liady marked this conversation as resolved.
Show resolved Hide resolved
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' } },
})
})
})
Loading
Loading