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

chore: Remove style-dictionary dependency from React Native package #5848

Merged
merged 21 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions .changeset/sharp-kiwis-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@aws-amplify/ui-react-native": patch
"@aws-amplify/ui": patch
---

chore: Copy `style-dictionary` functions to ui package and export from there. Remove `style-dictionary` dependency from React Native package.
3 changes: 1 addition & 2 deletions packages/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@
"dependencies": {
"@aws-amplify/ui": "6.6.2",
"@aws-amplify/ui-react-core": "3.0.26",
"@aws-amplify/ui-react-core-notifications": "2.0.26",
"style-dictionary": "3.9.1"
"@aws-amplify/ui-react-core-notifications": "2.0.26"
},
"peerDependencies": {
"aws-amplify": "^6.6.0",
Expand Down
31 changes: 13 additions & 18 deletions packages/react-native/src/theme/createTheme.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import deepExtend from 'style-dictionary/lib/utils/deepExtend';
import resolveObject from 'style-dictionary/lib/utils/resolveObject';
import usesReference from 'style-dictionary/lib/utils/references/usesReference';
import { deepExtend, resolveObject, usesReference } from '@aws-amplify/ui';
import { isFunction, setupTokens } from '@aws-amplify/ui';
import {
Theme,
Expand All @@ -15,13 +13,15 @@ import { defaultTheme } from './defaultTheme';
// calling the component theme function with the already resolved base tokens
// OR
// resolving the component theme object
interface TokensAndComponents {
components: Components;
tokens: StrictTokens;
}

const setupComponents = ({
components,
tokens,
}: {
components: Components;
tokens: StrictTokens;
}) => {
}: TokensAndComponents): Components => {
const output = components
? Object.entries(components).reduce(
(acc, [key, value]) => ({
Expand All @@ -32,7 +32,7 @@ const setupComponents = ({
)
: {};

return resolveObject({
return resolveObject<TokensAndComponents>({
...tokens,
components: output,
}).components;
Expand Down Expand Up @@ -103,12 +103,7 @@ export const createTheme = (
): StrictTheme => {
// merge custom `theme` param and `StrictTheme` to get the merged theme.
// `deepExtend` is a Style Dictionary method that performs a deep merge on n objects.
const mergedTheme = deepExtend([
{},
defaultTheme,
theme,
// cast to `StrictTheme` as `deepExtend` returns a generic object
]) as StrictTheme;
const mergedTheme = deepExtend<StrictTheme>([{}, defaultTheme, theme]);

let { tokens: mergedTokens } = mergedTheme;
const { spaceModifier = 1 } = mergedTheme;
Expand All @@ -119,11 +114,11 @@ export const createTheme = (
if (theme?.overrides?.length) {
theme.overrides.forEach((override) => {
if (override?.colorMode === colorMode) {
mergedTokens = deepExtend([
mergedTokens = deepExtend<StrictTheme['tokens']>([
{},
mergedTokens,
override.tokens,
]) as StrictTheme['tokens'];
]);
}
// more overrides in the future could happen here
});
Expand All @@ -132,13 +127,13 @@ export const createTheme = (
// Setup the tokens:
// - each token will have a raw value
// - references to tokens (strings wrapped in curly braces) are replaced by raw values
const tokens = resolveObject(
const tokens = resolveObject<StrictTheme['tokens']>(
setupTokens({
tokens: mergedTokens,
setupToken: ({ token, path }) => {
return setupToken({ token, path, spaceModifier });
},
}) as StrictTheme['tokens']
})
);

let components;
Expand Down
2 changes: 0 additions & 2 deletions packages/ui/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ const config: Config = {
'!<rootDir>/src/index.ts',
// ignore internal `debugUtils` from coverage thresholds
'!<rootDir>/**/debugUtils.ts',
// ignore coverage for style-dictionary type declaration file
'!<rootDir>/src/theme/types/style-dictionary.d.ts',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

!<rootDir>/src/theme/types/style-dictionary.d.ts does not exist anymore

],
coverageThreshold: {
global: {
Expand Down
269 changes: 269 additions & 0 deletions packages/ui/src/theme/createTheme/__tests__/resolveObject.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import { resolveObject } from '../resolveObject';

describe('resolveObject', () => {
it('should not mutate the original object', () => {
const original = {
test: '1',
a: {
b: {
c: 1,
d: '{e.f.g}',
},
},
e: {
f: {
g: 2,
h: '{a.b.c}',
},
},
i: '{e.f.g}',
};
const test = resolveObject(original);
expect(original).toHaveProperty('a.b.d', '{e.f.g}');
expect(test).toHaveProperty('a.b.d', 2);
});

it('should do simple references', () => {
const test = resolveObject({
foo: 'bar',
bar: '{foo}',
});
expect(test).toHaveProperty('bar', 'bar');
});

it('should do simple interpolation for both strings and numbers', () => {
const test = resolveObject({
a: 'test1 value',
b: 123,
c: '{a} text after',
d: 'text before {a}',
e: 'text before {a} text after',
f: '{b} text after',
g: 'text before {b}',
h: 'text before {b} text after',
});
expect(test).toHaveProperty('c', 'test1 value text after');
expect(test).toHaveProperty('d', 'text before test1 value');
expect(test).toHaveProperty('e', 'text before test1 value text after');
expect(test).toHaveProperty('f', '123 text after');
expect(test).toHaveProperty('g', 'text before 123');
expect(test).toHaveProperty('h', 'text before 123 text after');
});

it('should do nested references', () => {
const obj = {
test: '1',
a: {
b: {
c: 1,
d: '{e.f.g}',
},
},
e: {
f: {
g: 2,
h: '{a.b.c}',
},
},
i: '{e.f.g}',
};
const test = resolveObject(obj);
expect(test).toHaveProperty('i', 2);
expect(test).toHaveProperty('a.b.d', 2);
expect(test).toHaveProperty('e.f.h', 1);
});

it('should handle nested pointers', () => {
const test = resolveObject({
a: 1,
b: '{a}',
c: '{b}',
d: '{e}',
e: '{c}',
});
expect(test).toHaveProperty('b', 1);
expect(test).toHaveProperty('c', 1);
});

it('should handle deep nested pointers', () => {
const test = resolveObject({
a: '{b}',
b: '{c}',
c: '{d}',
d: '{e}',
e: '{f}',
f: '{g}',
g: 1,
});
expect(test).toHaveProperty('a', 1);
expect(test).toHaveProperty('b', 1);
expect(test).toHaveProperty('c', 1);
expect(test).toHaveProperty('d', 1);
expect(test).toHaveProperty('e', 1);
expect(test).toHaveProperty('f', 1);
expect(test).toHaveProperty('g', 1);
});

it('should handle deep nested pointers with string interpolation', () => {
const test = resolveObject({
a: '{b} bar',
b: '{c} baz',
c: '{d} bla',
d: '{e} boo',
e: '{f} bae',
f: '{g} bee',
g: 'foo bon',
});
expect(test).toHaveProperty('a', 'foo bon bee bae boo bla baz bar');
expect(test).toHaveProperty('b', 'foo bon bee bae boo bla baz');
expect(test).toHaveProperty('c', 'foo bon bee bae boo bla');
expect(test).toHaveProperty('d', 'foo bon bee bae boo');
expect(test).toHaveProperty('e', 'foo bon bee bae');
expect(test).toHaveProperty('f', 'foo bon bee');
expect(test).toHaveProperty('g', 'foo bon');
});

it('should handle deep nested pointers and nested references', () => {
const test = resolveObject({
a: {
a: {
a: '{b.b.b}',
},
},
b: {
b: {
b: '{c.c.c}',
},
},
c: {
c: {
c: '{d.d.d}',
},
},
d: {
d: {
d: '{e.e.e}',
},
},
e: {
e: {
e: '{f.f.f}',
},
},
f: {
f: {
f: '{g.g.g}',
},
},
g: {
g: {
g: 1,
},
},
});
expect(test).toHaveProperty('a.a.a', 1);
expect(test).toHaveProperty('b.b.b', 1);
expect(test).toHaveProperty('c.c.c', 1);
expect(test).toHaveProperty('d.d.d', 1);
expect(test).toHaveProperty('e.e.e', 1);
expect(test).toHaveProperty('f.f.f', 1);
expect(test).toHaveProperty('g.g.g', 1);
});

it('should keep the type of the referenced property', () => {
const referenceType = {
a: 1,
b: {
c: 2,
},
d: '{a}',
e: '{b}',
f: [1, 2, 3],
g: '{f}',
};
const test = resolveObject<typeof referenceType>(referenceType);
expect(test).toHaveProperty('d', 1);
expect(typeof test.d).toBe('number');
expect(typeof test.e).toBe('object');
expect(Array.isArray(test.f)).toBeTruthy();
expect(test).toHaveProperty('e.c', 2);
});

it('should handle and evaluate items in an array', () => {
const withArray = {
a: 1,
b: {
c: 2,
},
d: ['{b.c}', '{a}'],
e: [
{
a: '{a}',
},
{
a: '{b.c}',
},
],
};
const test = resolveObject<typeof withArray>(withArray);
expect(test.d[0]).toBe(2);
expect(test.d[1]).toBe(1);
expect(test.e[0].a).toBe(1);
expect(test.e[1].a).toBe(2);
});

it('should correctly replace multiple references without reference errors', function () {
const obj = resolveObject({
prop1: { value: 'test1 value' },
prop2: { value: 'test2 value' },
prop3: { value: '{prop1.value}' },
prop4: { value: '{prop3.value}' },
prop5: { value: 5 },
prop6: { value: 6 },
prop7: { value: '{prop5.value}' },
prop8: { value: '{prop7.value}' },
prop12: { value: '{prop1.value}, {prop2.value} and some extra stuff' },
prop124: { value: '{prop1.value}, {prop2.value} and {prop4.value}' },
prop15: { value: '{prop1.value}, {prop5.value} and some extra stuff' },
prop156: { value: '{prop1.value}, {prop5.value} and {prop6.value}' },
prop1568: {
value: '{prop1.value}, {prop5.value}, {prop6.value} and {prop8.value}',
},
});
expect(JSON.stringify(obj)).toBe(
JSON.stringify({
prop1: { value: 'test1 value' },
prop2: { value: 'test2 value' },
prop3: { value: 'test1 value' },
prop4: { value: 'test1 value' },
prop5: { value: 5 },
prop6: { value: 6 },
prop7: { value: 5 },
prop8: { value: 5 },
prop12: { value: 'test1 value, test2 value and some extra stuff' },
prop124: { value: 'test1 value, test2 value and test1 value' },
prop15: { value: 'test1 value, 5 and some extra stuff' },
prop156: { value: 'test1 value, 5 and 6' },
prop1568: { value: 'test1 value, 5, 6 and 5' },
})
);
});

it('should handle spaces', () => {
const withSpaces = {
foo: { value: 'bar' },
bar: { value: '{ foo.value }' },
};
const test = resolveObject<typeof withSpaces>(withSpaces);
expect(test).toHaveProperty('foo.value', test.bar.value);
});

it('should handle 0', () => {
const withZero = {
test: { value: '{zero.value}' },
zero: { value: 0 },
};
const test = resolveObject<typeof withZero>(withZero);
expect(test.test.value).toBe(0);
});
});
Loading
Loading