Skip to content

Commit

Permalink
Add css/mediaQuery formatter and replace use of cssWrapMediaQuery (#926)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasoppermann committed Apr 24, 2024
1 parent 0697ada commit 8357f04
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 74 deletions.
5 changes: 5 additions & 0 deletions .changeset/eighty-oranges-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/primitives': minor
---

Add css/mediaQuery formatter and replace use of cssWrapMediaQuery
6 changes: 3 additions & 3 deletions src/PrimerStyleDictionary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
cssCustomMedia,
jsonOneDimensional,
jsonPostCssFallback,
cssWrapMediaQuery,
cssMediaQuery,
cssVariables,
jsonFigma,
} from './formats'
Expand All @@ -60,8 +60,8 @@ StyleDictionary.registerFormat({
})

StyleDictionary.registerFormat({
name: 'css/wrapMediaQuery',
formatter: cssWrapMediaQuery,
name: 'css/mediaQuery',
formatter: cssMediaQuery,
})

StyleDictionary.registerFormat({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
import {cssWrapMediaQuery} from './cssWrapMediaQuery'
import {cssMediaQuery} from './cssMediaQuery'
import {getMockDictionary, getMockFormatterArguments, getMockToken} from '../test-utilities'
import {format} from 'prettier'
import type {TransformedToken} from 'style-dictionary'

describe('Format: tokens nested in media query', () => {
it('Wraps in screen if no media query defined', () => {
it('Wraps in :root if no media query defined', () => {
const input = getMockFormatterArguments()
const expectedOutput = format(
` @media screen {
:root {
`:root {
--red: transformedValue;
}
}`,
{parser: 'css', printWidth: 500},
)
expect(cssWrapMediaQuery(input)).toStrictEqual(expectedOutput)
expect(cssMediaQuery(input)).toStrictEqual(expectedOutput)
})

it('Wraps in screen if no matching media query is found', () => {
it('Wrap all tokens in screen media query', () => {
const input = getMockFormatterArguments({
options: {
mediaQuery: {
'size-fine.css': '(pointer: fine)',
},
},
file: {
destination: 'size-coarse.css',
destination: 'tokens.ts',
options: {
showFileHeader: false,
queries: [
{
query: '@media screen',
},
],
},
},
})
Expand All @@ -38,32 +37,61 @@ describe('Format: tokens nested in media query', () => {
}`,
{parser: 'css', printWidth: 500},
)
expect(cssWrapMediaQuery(input)).toStrictEqual(expectedOutput)
expect(cssMediaQuery(input)).toStrictEqual(expectedOutput)
})

it('Wraps in defined media query if files match', () => {
it('Ignore if no matching media query is found', () => {
const input = getMockFormatterArguments({
options: {
mediaQuery: {
'size-fine.css': '(pointer: fine)',
file: {
destination: 'size-coarse.css',
options: {
queries: [
{
query: '@media screen',
matcher: (token: TransformedToken) => token.filePath.includes('desktop'),
},
],
showFileHeader: false,
},
},
})
expect(cssMediaQuery(input)).toStrictEqual('')
})

it('Wraps in defined media query if files match', () => {
const input = getMockFormatterArguments({
dictionary: getMockDictionary({
tokens: {
subgroup: {
gap: getMockToken({
name: 'tokens-subgroup-gap',
filePath: 'size-fine.json',
}),
},
},
}),
file: {
destination: 'size-fine.css',
options: {
showFileHeader: false,
queries: [
{
query: '@media (pointer: fine)',
matcher: (token: TransformedToken) => token.filePath.includes('size-fine.json'),
},
],
},
},
})
const expectedOutput = format(
` @media (pointer: fine) {
:root {
--red: transformedValue;
--tokens-subgroup-gap: transformedValue;
}
}`,
{parser: 'css', printWidth: 500},
)
expect(cssWrapMediaQuery(input)).toStrictEqual(expectedOutput)
expect(cssMediaQuery(input)).toStrictEqual(expectedOutput)
})

it('Show comment if option.description is true', () => {
Expand All @@ -80,28 +108,30 @@ describe('Format: tokens nested in media query', () => {
},
}),
options: {
mediaQuery: {
'size-fine.css': '(pointer: fine)',
},
descriptions: true,
},
file: {
destination: 'size-fine.css',
options: {
showFileHeader: false,
queries: [
{
query: '@media (prefers-color-scheme: light)',
},
],
},
},
})

const expectedOutput = format(
` @media (pointer: fine) {
` @media (prefers-color-scheme: light){
:root {
--red: transformedValue; /* This is a description */
}
}`,
{parser: 'css', printWidth: 500},
)
expect(cssWrapMediaQuery(input)).toStrictEqual(expectedOutput)
expect(cssMediaQuery(input)).toStrictEqual(expectedOutput)
})

it('Hides comment if option.description is false or not set', () => {
Expand All @@ -118,15 +148,17 @@ describe('Format: tokens nested in media query', () => {
},
}),
options: {
mediaQuery: {
'size-fine.css': '(pointer: fine)',
},
descriptions: false,
},
file: {
destination: 'size-fine.css',
options: {
showFileHeader: false,
queries: [
{
query: '@media (prefers-color-scheme: dark)',
},
],
},
},
})
Expand All @@ -143,28 +175,29 @@ describe('Format: tokens nested in media query', () => {
},
},
}),
options: {
mediaQuery: {
'size-fine.css': '(pointer: fine)',
},
},
options: {}, // description not set
file: {
destination: 'size-fine.css',
options: {
showFileHeader: false,
queries: [
{
query: '@media (prefers-color-scheme: dark)',
},
],
},
},
})

const expectedOutput = format(
` @media (pointer: fine) {
` @media (prefers-color-scheme: dark) {
:root {
--red: transformedValue;
}
}`,
{parser: 'css', printWidth: 500},
)
expect(cssWrapMediaQuery(input)).toStrictEqual(expectedOutput)
expect(cssWrapMediaQuery(inputUnset)).toStrictEqual(expectedOutput)
expect(cssMediaQuery(input)).toStrictEqual(expectedOutput)
expect(cssMediaQuery(inputUnset)).toStrictEqual(expectedOutput)
})
})
95 changes: 95 additions & 0 deletions src/formats/cssMediaQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type {TransformedToken} from 'style-dictionary'
import StyleDictionary from 'style-dictionary'
import type {FormatterArguments} from 'style-dictionary/types/Format'
import {format} from 'prettier'
import type {LineFormatting} from 'style-dictionary/types/FormatHelpers'
const {fileHeader, formattedVariables} = StyleDictionary.formatHelpers

export const cssMediaQuery: StyleDictionary.Formatter = ({
dictionary: originalDictionary,
options = {
queries: [],
},
file,
platform,
}: FormatterArguments): string => {
// get options
const {outputReferences, descriptions} = options
// selector
const selector = file.options?.selector || ':root'
// query extension property
const queryExtProp = file.options?.queryExtensionProperty || 'mediaQuery'
// get queries from file options
const queries = file.options?.queries || [
{
query: undefined,
matcher: () => true,
},
]
// set formatting
const formatting: LineFormatting = {
commentStyle: descriptions ? 'long' : 'none',
}
// clone dictionary
const dictionary = {...originalDictionary}
// add prefix to tokens
if (platform.prefix) {
dictionary.allTokens = dictionary.allTokens.map(
token =>
({
...token,
name: `${platform.prefix}-${token.name}`,
path: [platform.prefix, ...token.path],
} as TransformedToken),
)
}
// get queries from tokens
for (const designToken of dictionary.allTokens) {
const query = designToken.$extensions?.[queryExtProp]
// early abort if query does not exist on token
if (!query) continue
// if query exists already from other token
const currentQueryIndex = queries.findIndex((q: {query: string; matcher: () => boolean}) => q.query === query)

// if query exists
if (currentQueryIndex > -1) {
queries[currentQueryIndex] = {
...queries[currentQueryIndex],
matcher: (token: TransformedToken) =>
queries[currentQueryIndex].matcher(token) ||
token.$extensions[queryExtProp] === queries[currentQueryIndex].query,
}
}
// if query does not exist
else {
queries.push({
query,
matcher: (token: TransformedToken) => token.$extensions?.[queryExtProp] === query,
})
}
}
// add file header
const output = [fileHeader({file})]
// add single theme css
for (const query of queries) {
const {query: queryString, matcher} = query
// filter tokens to only include the ones that pass the matcher
const filteredDictionary = {
...dictionary,
allTokens: dictionary.allTokens.filter(matcher || (() => true)),
}
// early abort if no matches
if (!filteredDictionary.allTokens.length) continue
// add tokens into root
const rootCss = `${selector} {${formattedVariables({
format: 'css',
dictionary: filteredDictionary,
outputReferences,
formatting,
})}}`
// add css with or without query
output.push(queryString ? `${queryString} { ${rootCss} }` : rootCss)
}
// return prettified
return format(output.join('\n'), {parser: 'css', printWidth: 500})
}
29 changes: 0 additions & 29 deletions src/formats/cssWrapMediaQuery.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/formats/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export {cssThemed} from './cssThemed'
export {cssCustomMedia} from './cssCustomMedia'
export {cssWrapMediaQuery} from './cssWrapMediaQuery'
export {cssMediaQuery} from './cssMediaQuery'
export {cssVariables} from './cssVariables'
export {jsonFigma} from './jsonFigma'
export {javascriptCommonJs} from './javascriptCommonJs'
Expand Down
Loading

0 comments on commit 8357f04

Please sign in to comment.