Skip to content

Commit

Permalink
Support area names in grid templates (#6080)
Browse files Browse the repository at this point in the history
**Problem:**

Parsed CSS grid templates should support area names.

**Fix:**

Parse area names along with the CSS number for grid templates. The label
is stored in the number itself, so we can reuse it later (e.g. for
targeting via other style props).

To make it more apparent, I added the area name under the resize
handles, but it's definitely not a requirement (although I think it
could/should be shown somewhere!).


https://github.com/user-attachments/assets/5aba975d-3b24-48be-8d69-c19f774bb351


Fixes #6079
  • Loading branch information
ruggi authored Jul 15, 2024
1 parent 8721237 commit bc45f62
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 7 deletions.
5 changes: 5 additions & 0 deletions editor/src/components/canvas/controls/grid-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ export const GridResizingControl = React.memo((props: GridResizingControlProps)
justifyContent: 'center',
cursor: gridEdgeToCSSCursor(props.axis === 'column' ? 'column-start' : 'row-start'),
fontSize: 8,
position: 'relative',
}}
css={{
opacity: resizing ? 1 : 0.5,
Expand All @@ -239,6 +240,10 @@ export const GridResizingControl = React.memo((props: GridResizingControlProps)
onMouseDown={mouseDownHandler}
>
{props.axis === 'row' ? '↕' : '↔'}
{when(
props.dimension.areaName != null,
<span style={{ position: 'absolute', top: 12 }}>{props.dimension.areaName}</span>,
)}
</div>
{when(
resizing,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1970,11 +1970,13 @@ export const CSSNumberKeepDeepEquality: KeepDeepEqualityCall<CSSNumber> = combin
)

export const GridCSSNumberKeepDeepEquality: KeepDeepEqualityCall<GridCSSNumber> =
combine2EqualityCalls(
combine3EqualityCalls(
(cssNum) => cssNum.value,
createCallWithTripleEquals<number>(),
(cssNum) => cssNum.unit,
nullableDeepEquality(createCallWithTripleEquals<GridCSSNumberUnit>()),
(cssNum) => cssNum.areaName,
nullableDeepEquality(StringKeepDeepEquality),
gridCSSNumber,
)

Expand Down
32 changes: 32 additions & 0 deletions editor/src/components/inspector/common/css-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import {
RegExpLibrary,
toggleSimple,
toggleStylePropPath,
tokenizeGridTemplate,
} from './css-utils'

describe('toggleStyleProp', () => {
Expand Down Expand Up @@ -1807,3 +1808,34 @@ describe('printBackgroundSize', () => {
`)
})
})

describe('tokenizeGridTemplate', () => {
it('tokenizes the grid template strings (no units)', async () => {
expect(tokenizeGridTemplate('123 456 78 9')).toEqual(['123', '456', '78', '9'])
})
it('tokenizes the grid template strings (with units)', async () => {
expect(tokenizeGridTemplate('123 456px 78 9rem')).toEqual(['123', '456px', '78', '9rem'])
})
it('tokenizes the grid template strings (with some area names)', async () => {
expect(tokenizeGridTemplate('[foo] 123 456px 78 9rem')).toEqual([
'[foo] 123',
'456px',
'78',
'9rem',
])
expect(tokenizeGridTemplate('123 [foo]456px 78 [bar] 9rem')).toEqual([
'123',
'[foo] 456px',
'78',
'[bar] 9rem',
])
})
it('tokenizes the grid template strings (with all area names)', async () => {
expect(tokenizeGridTemplate('[foo] 123 [bar]456px [baz] 78 [QUX]9rem')).toEqual([
'[foo] 123',
'[bar] 456px',
'[baz] 78',
'[QUX] 9rem',
])
})
})
57 changes: 51 additions & 6 deletions editor/src/components/inspector/common/css-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -589,12 +589,18 @@ const GridCSSNumberUnits: Array<GridCSSNumberUnit> = [...LengthUnits, ...Resolut
export interface GridCSSNumber {
value: number
unit: GridCSSNumberUnit | null
areaName: string | null
}

export function gridCSSNumber(value: number, unit: GridCSSNumberUnit | null): GridCSSNumber {
export function gridCSSNumber(
value: number,
unit: GridCSSNumberUnit | null,
areaName: string | null,
): GridCSSNumber {
return {
value,
unit,
value: value,
unit: unit,
areaName: areaName,
}
}

Expand Down Expand Up @@ -762,7 +768,8 @@ export function printArrayCSSNumber(array: Array<GridCSSNumber>): string {
return array
.map((dimension) => {
const printed = printCSSNumber(dimension, null)
return typeof printed === 'string' ? printed : `${printed}`
const areaName = dimension.areaName != null ? `[${dimension.areaName}] ` : ''
return `${areaName}${printed}`
})
.join(' ')
}
Expand Down Expand Up @@ -834,13 +841,30 @@ export const parseCSSUnitlessAsNumber = (input: unknown): Either<string, number>
}
}

const gridCSSTemplateNumberRegex = /^\[(.+)\]\s*(.+)$/

export function parseToCSSGridNumber(input: unknown): Either<string, GridCSSNumber> {
function getParts() {
if (typeof input === 'string') {
const match = input.match(gridCSSTemplateNumberRegex)
if (match != null) {
return {
areaName: match[1],
inputToParse: match[2],
}
}
}
return { areaName: null, inputToParse: input }
}
const { areaName, inputToParse } = getParts()

return mapEither((value) => {
return {
value: value.value,
unit: value.unit as GridCSSNumberUnit | null,
areaName: areaName,
}
}, parseCSSGrid(input))
}, parseCSSGrid(inputToParse))
}

export const parseCSSNumber = (
Expand Down Expand Up @@ -887,6 +911,27 @@ export function parseGridRange(input: unknown): Either<string, GridRange> {
}
}

const reGridAreaNameBrackets = /^\[.+\]$/

export function tokenizeGridTemplate(str: string): string[] {
let tokens: string[] = []
let parts = str.replace(/\]/g, '] ').split(/\s+/)

while (parts.length > 0) {
const part = parts.shift()?.trim()
if (part == null) {
break
}
if (part.match(reGridAreaNameBrackets) != null && parts.length > 0) {
const withAreaName = `${part} ${parts.shift()}`
tokens.push(withAreaName)
} else {
tokens.push(part)
}
}
return tokens
}

export function parseGridAutoOrTemplateBase(
input: unknown,
): Either<string, GridAutoOrTemplateBase> {
Expand All @@ -895,7 +940,7 @@ export function parseGridAutoOrTemplateBase(
return leftMapEither<string, ParseError, GridCSSNumber>(descriptionParseError, result)
}
if (typeof input === 'string') {
const parsedCSSArray = parseCSSArray([numberParse])(input.split(/ +/))
const parsedCSSArray = parseCSSArray([numberParse])(tokenizeGridTemplate(input))
return bimapEither(
(error) => {
if (error.type === 'DESCRIPTION_PARSE_ERROR') {
Expand Down

0 comments on commit bc45f62

Please sign in to comment.