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

fix typography tokens resolution #3157

Merged
merged 8 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
5 changes: 5 additions & 0 deletions .changeset/weak-oranges-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tokens-studio/figma-plugin": patch
---

fixes a bug where applying a typography token to a text node would override individual property changes (like font size or font family) when "Apply to selection/page/document" is clicked.
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ describe('Can set values on node', () => {
},
},
);
expect(setTextValuesOnTargetSpy).toHaveBeenCalled();
expect(setTextValuesOnTargetSpy).not.toHaveBeenCalled();
});
Comment on lines +268 to 269
Copy link
Contributor Author

@akshay-gupta7 akshay-gupta7 Oct 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This update in condition is in line with the updated logic, wherein, when a text node has both individual typography value tokens and a typography token, it will not call setTextValuesOnTarget directly, instead will route to tryApplyTypographyCompositeVariable.


it('sets textstyle if matching Style is found', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,80 +8,75 @@ import { NodeTokenRefMap } from '@/types/NodeTokenRefMap';
import { MapValuesToTokensResult } from '@/types';
import { tryApplyTypographyCompositeVariable } from './tryApplyTypographyCompositeVariable';

function buildResolvedValueObject(data: any) {
return {
fontFamily: isPrimitiveValue(data.fontFamilies) ? String(data.fontFamilies.startsWith('{') ? data.fontFamilies : `{${data.fontFamilies}}`) : undefined,
fontWeight: isPrimitiveValue(data.fontWeights) ? String(data.fontWeights.startsWith('{') ? data.fontWeights : `{${data.fontWeights}}`) : undefined,
lineHeight: isPrimitiveValue(data.lineHeights) ? String(data.lineHeights.startsWith('{') ? data.lineHeights : `{${data.lineHeights}}`) : undefined,
fontSize: isPrimitiveValue(data.fontSizes) ? String(data.fontSizes.startsWith('{') ? data.fontSizes : `{${data.fontSizes}}`) : undefined,
letterSpacing: isPrimitiveValue(data.letterSpacing) ? String(data.letterSpacing.startsWith('{') ? data.letterSpacing : `{${data.letterSpacing}}`) : undefined,
paragraphSpacing: isPrimitiveValue(data.paragraphSpacing) ? String(data.paragraphSpacing.startsWith('{') ? data.paragraphSpacing : `{${data.paragraphSpacing}}`) : undefined,
textCase: isPrimitiveValue(data.textCase) ? String(data.textCase.startsWith('{') ? data.textCase : `{${data.textCase}}`) : undefined,
textDecoration: isPrimitiveValue(data.textDecoration) ? String(data.textDecoration.startsWith('{') ? data.textDecoration : `{${data.textDecoration}}`) : undefined,
};
}

function buildValueObject(values: any, resolvedToken: any) {
const tokenValue = resolvedToken?.value || {};

return {
fontFamily: isPrimitiveValue(values.fontFamilies) ? String(values.fontFamilies) : tokenValue.fontFamily,
fontWeight: isPrimitiveValue(values.fontWeights) ? String(values.fontWeights) : tokenValue.fontWeight,
lineHeight: isPrimitiveValue(values.lineHeights) ? String(values.lineHeights) : tokenValue.lineHeight,
fontSize: isPrimitiveValue(values.fontSizes) ? String(values.fontSizes) : tokenValue.fontSize,
letterSpacing: isPrimitiveValue(values.letterSpacing) ? String(values.letterSpacing) : tokenValue.letterSpacing,
paragraphSpacing: isPrimitiveValue(values.paragraphSpacing) ? String(values.paragraphSpacing) : tokenValue.paragraphSpacing,
textCase: isPrimitiveValue(values.textCase) ? String(values.textCase) : tokenValue.textCase,
textDecoration: isPrimitiveValue(values.textDecoration) ? String(values.textDecoration) : tokenValue.textDecoration,
};
}

export async function applyTypographyTokenOnNode(
node: BaseNode,
data: NodeTokenRefMap,
values: MapValuesToTokensResult,
baseFontSize: string,
) {
if (!(node.type === 'TEXT')) return;
if (values.typography && data.typography) {
const resolvedToken = defaultTokenValueRetriever.get(data.typography);
let matchingStyleId = resolvedToken.styleId;

// Note: We should remove "backup style id" logic from here (this part). This was relevant before we had Themes, where style ids could not be saved to a token yet.
if (!matchingStyleId && isSingleTypographyValue(resolvedToken.value)) {
const styleIdBackupKey = 'textStyleId_original';
const nonLocalStyle = getNonLocalStyle(node, styleIdBackupKey, 'typography');
if (nonLocalStyle) {
if (textStyleMatchesTypographyToken(nonLocalStyle, resolvedToken.value, baseFontSize)) {
// Non-local style matches - use this and clear style id backup:
matchingStyleId = nonLocalStyle.id;
clearStyleIdBackup(node, styleIdBackupKey);
} else if (resolvedToken.adjustedTokenName === nonLocalStyle.name) {
// Non-local style does NOT match, but style name and token path does,
// so we assume selected token value is an override (e.g. dark theme)
// Now backup up style id before overwriting with raw token value, so we
// can re-link the non-local style, when the token value matches again:
setStyleIdBackup(node, styleIdBackupKey, nonLocalStyle.id);
}
}
}
const resolvedToken = data.typography ? defaultTokenValueRetriever.get(data.typography) : null;
let matchingStyleId = resolvedToken?.styleId;

if (
node.type === 'TEXT'
&& (!matchingStyleId || (matchingStyleId && !(await trySetStyleId(node, 'text', matchingStyleId))))
) {
if (isSingleTypographyValue(resolvedToken.value)) {
setTextValuesOnTarget(node, data.typography, baseFontSize);
}
// Backup logic for non-local styles
if (!matchingStyleId && resolvedToken && isSingleTypographyValue(resolvedToken.value)) {
const styleIdBackupKey = 'textStyleId_original';
const nonLocalStyle = getNonLocalStyle(node, styleIdBackupKey, 'typography');
if (nonLocalStyle && textStyleMatchesTypographyToken(nonLocalStyle, resolvedToken.value, baseFontSize)) {
matchingStyleId = nonLocalStyle.id;
clearStyleIdBackup(node, styleIdBackupKey);
} else if (nonLocalStyle && resolvedToken.adjustedTokenName === nonLocalStyle?.name) {
setStyleIdBackup(node, styleIdBackupKey, nonLocalStyle.id);
}
}
if (
values.fontFamilies
|| values.fontWeights
|| values.lineHeights
|| values.fontSizes
|| values.letterSpacing
|| values.paragraphSpacing
|| values.textCase
|| values.textDecoration
) {
const resolvedValueObject = {
fontFamily: isPrimitiveValue(data.fontFamilies) ? String(data.fontFamilies.startsWith('{') ? data.fontFamilies : `{${data.fontFamilies}}`) : undefined,
fontWeight: isPrimitiveValue(data.fontWeights) ? String(data.fontWeights.startsWith('{') ? data.fontWeights : `{${data.fontWeights}}`) : undefined,
lineHeight: isPrimitiveValue(data.lineHeights) ? String(data.lineHeights.startsWith('{') ? data.lineHeights : `{${data.lineHeights}}`) : undefined,
fontSize: isPrimitiveValue(data.fontSizes) ? String(data.fontSizes.startsWith('{') ? data.fontSizes : `{${data.fontSizes}}`) : undefined,
letterSpacing: isPrimitiveValue(data.letterSpacing) ? String(data.letterSpacing.startsWith('{') ? data.letterSpacing : `{${data.letterSpacing}}`) : undefined,
paragraphSpacing: isPrimitiveValue(data.paragraphSpacing) ? String(data.paragraphSpacing.startsWith('{') ? data.paragraphSpacing : `{${data.paragraphSpacing}}`) : undefined,
textCase: isPrimitiveValue(data.textCase) ? String(data.textCase.startsWith('{') ? data.textCase : `{${data.textCase}}`) : undefined,
textDecoration: isPrimitiveValue(data.textDecoration) ? String(data.textDecoration.startsWith('{') ? data.textDecoration : `{${data.textDecoration}}`) : undefined,
}
const valueObject = {
fontFamily: isPrimitiveValue(values.fontFamilies) ? String(values.fontFamilies) : undefined,
fontWeight: isPrimitiveValue(values.fontWeights) ? String(values.fontWeights) : undefined,
lineHeight: isPrimitiveValue(values.lineHeights) ? String(values.lineHeights) : undefined,
fontSize: isPrimitiveValue(values.fontSizes) ? String(values.fontSizes) : undefined,
letterSpacing: isPrimitiveValue(values.letterSpacing) ? String(values.letterSpacing) : undefined,
paragraphSpacing: isPrimitiveValue(values.paragraphSpacing) ? String(values.paragraphSpacing) : undefined,
textCase: isPrimitiveValue(values.textCase) ? String(values.textCase) : undefined,
textDecoration: isPrimitiveValue(values.textDecoration) ? String(values.textDecoration) : undefined,
};
await tryApplyTypographyCompositeVariable({
target: node,
value: valueObject,
resolvedValue: resolvedValueObject,
baseFontSize,
});

// Apply matching style or fallback to applying values
if (matchingStyleId && (await trySetStyleId(node, 'text', matchingStyleId))) return;

// Apply typography token directly if no other properties exist
if (data.typography && resolvedToken && isSingleTypographyValue(resolvedToken.value) && !Object.keys(values).length) {
setTextValuesOnTarget(node, data.typography, baseFontSize);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now we will call this setTextValuesOnTarget here when a text Node has only a typography token applied to it

return;
}
}

// Build the resolved value and value objects
const resolvedValueObject = buildResolvedValueObject(data);
const valueObject = buildValueObject(values, resolvedToken);

// Apply the typography token and other values together
await tryApplyTypographyCompositeVariable({
target: node,
value: valueObject,
resolvedValue: resolvedValueObject,
baseFontSize,
});
}
Loading