-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Create Sass color() function migration
#7375
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
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
674de44
Create Sass color function migration
sam-b-rose 2787464
Add more color mappings
sam-b-rose 9e08907
Reduce color mappings
sam-b-rose 499c1d0
Move delc.prop check after fallback replacement
sam-b-rose 4727307
Improve color mappings and add fill mapping
sam-b-rose 5f4274b
Fix color migration tests
sam-b-rose fc1137e
Ignore color replacements in rgba functions
sam-b-rose aa8fbb3
Clean up color map file
sam-b-rose 4207fba
Add details about color mapping limitations to README.md
sam-b-rose d950774
Use getFunctionArgs for color function
sam-b-rose 9761391
Refine color function logic and types clean up
sam-b-rose File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@shopify/polaris-migrator': minor | ||
| --- | ||
|
|
||
| Add Sass color function migration |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
244 changes: 244 additions & 0 deletions
244
polaris-migrator/src/migrations/replace-sass-color/replace-sass-color.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,244 @@ | ||
| import type {FileInfo, API, Options} from 'jscodeshift'; | ||
| import postcss, {Plugin} from 'postcss'; | ||
| import valueParser from 'postcss-value-parser'; | ||
| import {colors as tokenColors, createVar} from '@shopify/polaris-tokens'; | ||
|
|
||
| import { | ||
| NamespaceOptions, | ||
| namespace, | ||
| getFunctionArgs, | ||
| stripQuotes, | ||
| StopWalkingFunctionNodes, | ||
| } from '../../utilities/sass'; | ||
| import {isKeyOf} from '../../utilities/type-guards'; | ||
|
|
||
| export default function replaceSassColors( | ||
| file: FileInfo, | ||
| _: API, | ||
| options: Options, | ||
| ) { | ||
| return postcss(plugin(options)).process(file.source, { | ||
| syntax: require('postcss-scss'), | ||
| }).css; | ||
| } | ||
|
|
||
| const processed = Symbol('processed'); | ||
|
|
||
| interface PluginOptions extends Options, NamespaceOptions {} | ||
|
|
||
| const plugin = (options: PluginOptions = {}): Plugin => { | ||
| const namespacedColor = namespace('color', options); | ||
|
|
||
| return { | ||
| postcssPlugin: 'replace-sass-color', | ||
| Declaration(decl) { | ||
| // @ts-expect-error - Skip if processed so we don't process it again | ||
| if (decl[processed]) return; | ||
|
|
||
| if (!isKeyOf(propertyMaps, decl.prop)) return; | ||
| const replacementMap = propertyMaps[decl.prop]; | ||
| const parsed = valueParser(decl.value); | ||
|
|
||
| parsed.walk((node) => { | ||
| if (node.type !== 'function') return; | ||
|
|
||
| if (node.value === 'rgba') { | ||
| return StopWalkingFunctionNodes; | ||
| } | ||
|
|
||
| // 1. Remove color() fallbacks | ||
| if (node.value === 'var') { | ||
| const args = getFunctionArgs(node); | ||
| const polarisCustomPropertyIndex = args.findIndex((arg) => | ||
| polarisCustomPropertyRegEx.test(arg), | ||
| ); | ||
| const colorFnFallbackIndex = args.findIndex((arg) => | ||
| arg.startsWith(namespacedColor), | ||
| ); | ||
|
|
||
| if (polarisCustomPropertyIndex < colorFnFallbackIndex) { | ||
| node.nodes = [node.nodes[0]]; | ||
| } | ||
|
|
||
| return StopWalkingFunctionNodes; | ||
| } | ||
|
|
||
| // 2. Replace `color()` with variable | ||
| if (node.value === namespacedColor) { | ||
| const colorFnArgs = getFunctionArgs(node).map(stripQuotes); | ||
| const hue = colorFnArgs[0] ?? ''; | ||
| const value = colorFnArgs[1] ?? 'base'; | ||
| const forBackground = colorFnArgs[2]; | ||
|
|
||
| // Skip color() with for-background argument | ||
| if (forBackground) return; | ||
|
|
||
| // Skip if we don't have a color for the hue and value | ||
| if ( | ||
| !( | ||
| isKeyOf(replacementMap, hue) && | ||
| isKeyOf(replacementMap[hue], value) | ||
| ) | ||
| ) | ||
| return; | ||
|
|
||
| const colorCustomProperty: string = replacementMap[hue][value]; | ||
|
|
||
| node.value = 'var'; | ||
| node.nodes = [ | ||
| { | ||
| type: 'word', | ||
| value: colorCustomProperty, | ||
| sourceIndex: node.nodes[0]?.sourceIndex ?? 0, | ||
| sourceEndIndex: colorCustomProperty.length, | ||
| }, | ||
| ]; | ||
| } | ||
| }); | ||
|
|
||
| decl.value = parsed.toString(); | ||
|
|
||
| // @ts-expect-error - Mark the declaration as processed | ||
| decl[processed] = true; | ||
| }, | ||
| }; | ||
| }; | ||
|
|
||
| /* | ||
| * See the legacy Sass API file for the original color palette | ||
| * documentation/guides/legacy-polaris-v8-public-api.scss | ||
| */ | ||
|
|
||
| const colorMap = { | ||
| blue: { | ||
| dark: '--p-interactive-hovered', | ||
| base: '--p-interactive', | ||
| }, | ||
| green: { | ||
| dark: '--p-text-success', | ||
| base: '--p-text-success', | ||
| }, | ||
| yellow: { | ||
| dark: '--p-text-warning', | ||
| base: '--p-text-warning', | ||
| }, | ||
| red: { | ||
| dark: '--p-text-critical', | ||
| base: '--p-text-critical', | ||
| }, | ||
| ink: { | ||
| base: '--p-text', | ||
| light: '--p-text-subdued', | ||
| lighter: '--p-text-subdued', | ||
| lightest: '--p-text-subdued', | ||
| }, | ||
| sky: { | ||
| dark: '--p-text-subdued-on-dark', | ||
| base: '--p-text-on-dark', | ||
| light: '--p-text-on-dark', | ||
| lighter: '--p-text-on-dark', | ||
| }, | ||
| black: { | ||
| base: '--p-text', | ||
| }, | ||
| white: { | ||
| base: '--p-text-on-dark', | ||
| }, | ||
| }; | ||
|
|
||
| const backgroundColorMap = { | ||
| green: { | ||
| light: '--p-surface-success', | ||
| lighter: '--p-surface-success-subdued', | ||
| }, | ||
| yellow: { | ||
| light: '--p-surface-warning', | ||
| lighter: '--p-surface-warning-subdued', | ||
| }, | ||
| red: { | ||
| light: '--p-surface-critical', | ||
| lighter: '--p-surface-critical-subdued', | ||
| }, | ||
| ink: { | ||
| dark: '--p-surface-dark', | ||
| base: '--p-surface-neutral-subdued-dark', | ||
| }, | ||
| sky: { | ||
| base: '--p-surface-neutral', | ||
| light: '--p-surface-neutral-subdued', | ||
| lighter: '--p-surface-subdued', | ||
| }, | ||
| black: { | ||
| base: '--p-surface-dark', | ||
| }, | ||
| white: { | ||
| base: '--p-surface', | ||
| }, | ||
| }; | ||
|
|
||
| const borderColorMap = { | ||
| green: { | ||
| dark: '--p-border-success', | ||
| base: '--p-border-success', | ||
| light: '--p-border-success-subdued', | ||
| lighter: '--p-border-success-subdued', | ||
| }, | ||
| yellow: { | ||
| dark: '--p-border-warning', | ||
| base: '--p-border-warning', | ||
| light: '--p-border-warning-disabled', | ||
| lighter: '--p-border-warning-subdued', | ||
| }, | ||
| red: { | ||
| dark: '--p-border-critical', | ||
| base: '--p-border-critical', | ||
| light: '--p-border-critical-subdued', | ||
| lighter: '--p-border-critical-subdued', | ||
| }, | ||
| ink: { | ||
| lightest: '--p-border', | ||
| }, | ||
| sky: { | ||
| light: '--p-border-subdued', | ||
| }, | ||
| }; | ||
|
|
||
| const fillColorMap = { | ||
| green: { | ||
| dark: '--p-icon-success', | ||
| base: '--p-icon-success', | ||
| }, | ||
| yellow: { | ||
| dark: '--p-icon-warning', | ||
| base: '--p-icon-warning', | ||
| }, | ||
| red: { | ||
| dark: '--p-icon-critical', | ||
| base: '--p-icon-critical', | ||
| }, | ||
| ink: { | ||
| base: '--p-icon', | ||
| light: '--p-icon', | ||
| lighter: '--p-icon-subdued', | ||
| lightest: '--p-icon-disabled', | ||
| }, | ||
| black: { | ||
| base: '--p-icon', | ||
| }, | ||
| white: { | ||
| base: '--p-icon-on-dark', | ||
| }, | ||
| }; | ||
|
|
||
| const propertyMaps = { | ||
| color: colorMap, | ||
| background: backgroundColorMap, | ||
| 'background-color': backgroundColorMap, | ||
| border: borderColorMap, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yes, great idea! 👍 |
||
| 'border-color': borderColorMap, | ||
| fill: fillColorMap, | ||
| }; | ||
|
|
||
| const polarisCustomPropertyRegEx = new RegExp( | ||
| Object.keys(tokenColors).map(createVar).join('|'), | ||
| ); | ||
79 changes: 79 additions & 0 deletions
79
polaris-migrator/src/migrations/replace-sass-color/tests/replace-sass-color.input.scss
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| .my-class { | ||
| // color | ||
| color: color('blue'); | ||
| color: color('blue', 'dark'); | ||
| color: color('green'); | ||
| color: color('green', 'dark'); | ||
| color: color('yellow'); | ||
| color: color('yellow', 'dark'); | ||
| color: color('red'); | ||
| color: color('red', 'dark'); | ||
| color: color('ink'); | ||
| color: color('ink', 'light'); | ||
| color: color('ink', 'lighter'); | ||
| color: color('ink', 'lightest'); | ||
| color: color('sky'); | ||
| color: color('sky', 'dark'); | ||
| color: color('sky', 'light'); | ||
| color: color('sky', 'lighter'); | ||
| color: color('black'); | ||
| color: color('white'); | ||
|
|
||
| // background | ||
| background-color: color('green', 'light'); | ||
| background-color: color('green', 'lighter'); | ||
| background-color: color('yellow', 'light'); | ||
| background-color: color('yellow', 'lighter'); | ||
| background-color: color('red', 'light'); | ||
| background-color: color('red', 'lighter'); | ||
| background-color: color('ink'); | ||
| background-color: color('ink', 'dark'); | ||
| background-color: color('sky'); | ||
| background-color: color('sky', 'light'); | ||
| background-color: color('sky', 'lighter'); | ||
| background-color: color('black'); | ||
| background-color: color('white'); | ||
|
|
||
| // border | ||
| border-color: color('green', 'dark'); | ||
| border-color: color('green'); | ||
| border-color: color('green', 'light'); | ||
| border-color: color('green', 'lighter'); | ||
| border-color: color('yellow', 'dark'); | ||
| border-color: color('yellow'); | ||
| border-color: color('yellow', 'light'); | ||
| border-color: color('yellow', 'lighter'); | ||
| border-color: color('red', 'dark'); | ||
| border-color: color('red'); | ||
| border-color: color('red', 'light'); | ||
| border-color: color('red', 'lighter'); | ||
| border-color: color('ink', 'lightest'); | ||
| border-color: color('sky', 'light'); | ||
|
|
||
| // fill | ||
| fill: color('green', 'dark'); | ||
| fill: color('green'); | ||
| fill: color('yellow', 'dark'); | ||
| fill: color('yellow'); | ||
| fill: color('red', 'dark'); | ||
| fill: color('red'); | ||
| fill: color('ink'); | ||
| fill: color('ink', 'light'); | ||
| fill: color('ink', 'lighter'); | ||
| fill: color('ink', 'lightest'); | ||
| fill: color('black'); | ||
| fill: color('white'); | ||
|
|
||
| // Remove color() fallbacks | ||
| color: var(--p-text, color('white')); | ||
|
|
||
| // Handle declarations with multiple values | ||
| border: var(--p-border-width-1) solid color('ink', 'lightest'); | ||
| background: border-box color('sky', 'light'); | ||
|
|
||
| // Skip color() with a for-background argument | ||
| color: color('ink', 'lighter', #f2ece4); | ||
|
|
||
| // Skip replacing color() within a function | ||
| background: rgba(color('black'), 0.5); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.