Skip to content

Commit

Permalink
feat(editor): show correct responsive value according to breakpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
liady committed Dec 12, 2024
1 parent 84da0ab commit 1b6a3a7
Show file tree
Hide file tree
Showing 7 changed files with 469 additions and 27 deletions.
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 }
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,11 @@ export var storyboard = (
const { flexDirection, gap } = styleInfo!
expect(flexDirection).toMatchObject({
type: 'property',
tags: [],
value: 'column',
propertyValue: { value: 'column' },
})
expect(gap).toMatchObject({
type: 'property',
tags: [],
value: cssNumber(2, 'rem'),
propertyValue: { value: '2rem' },
})
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,229 @@
import type { Config } from 'tailwindcss/types/config'
import { getModifiers, screensConfigToScreenSizes } from './tailwind-responsive-utils'

describe('getModifiers', () => {
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 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

0 comments on commit 1b6a3a7

Please sign in to comment.