@elliemae/ds-system 3.33.0-next.0 → 3.33.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,252 @@
1
+ ### Internal Workings of the Styled-Component Wrapper
2
+
3
+ This document delves into the internal mechanics of our custom styled-component wrapper, providing insights for developers who need to understand or extend its functionality.
4
+
5
+ #### Overview
6
+ The wrapper is designed to extend the standard functionality of styled-components with additional features like automatic theme coercion, design system integration, and automatic attribute generation. It acts as a higher-order component (HOC) that wraps around the base styled component.
7
+
8
+ #### Understanding Tagged Template Literals
9
+ In standard styled-components usage, styled components are often defined using tagged template literals, a feature of ES6. For example:
10
+
11
+ ```typescript
12
+ const StyledButton = styled.button`
13
+ margin: ${({ theme }) => theme.space.xs};
14
+ `;
15
+ ```
16
+
17
+ This syntax is concise and readable, especially for defining component styles that directly leverage props and themes.
18
+
19
+ But, for the purpose of this document, you should understand the real way this gets "transpiled" to:
20
+
21
+ ```typescript
22
+ styled('button')(['margin: ', ';\n'], ({theme}) => theme.space.xs);
23
+ ```
24
+
25
+ So, `styled` is a function that takes a tag (in this case, `'button'`) and returns a function that takes any number of arguments. The first argument is an array of strings (the constant parts of the template literal), and the rest are the interpolated values (the dynamic parts of the template literal). Each interpolated value will be inserted into the array of strings at the corresponding index.
26
+
27
+ This is just for the most-used-case on our library though. Styled components also accept object instead of tagged template literals, and we have to handle that as well. So keep that in mind as well.
28
+
29
+ #### The idea of the Wrapper
30
+
31
+ The wrapper is designed to also receive as a parameter an options object, along with the tag. So the signature of the wrapper is:
32
+
33
+ ```typescript
34
+ const OurStyled = (tag: Tag, options: {name: string, slot: string}) => ReturnType<styled('tag')>
35
+ ```
36
+
37
+ These name and slot properties will be used for theming the styled component, for applying style and variant overrides from the design system, calculate data-testids, data-dimsum-slot and other attributes as well, ref-merging, etc.
38
+
39
+ #### Let's see the code
40
+
41
+ ```typescript
42
+ checkNamingConvention(componentName, componentSlot);
43
+ ```
44
+
45
+ This function checks if the `name` and `slot` properties adhere to our project's naming conventions. If they don't, it triggers a warning.
46
+
47
+ The correct format for componentName is `DSComponentName`, and for componentSlot is `component-slot`. This is important since other functionalities rely on these properties being correctly formatted.
48
+
49
+ Now, let's look at the overall body of the function
50
+
51
+ ```typescript
52
+ const baseStyledResolver = baseStyled(tag) as unknown as ThemedStyledFunction<C, Theme, {}, keyof any>;
53
+
54
+ const dsStyledResolver: ThemedStyledFunctionBase<C, Theme> = (styleArg, ...expressions) => {
55
+ ... some code that we will see later ...
56
+ };
57
+
58
+ // Here we use arrow functions to preserve the `this` context
59
+ const styledAttributes = {
60
+ attrs: ((...args) => {
61
+ baseStyledResolver.attrs(...args);
62
+ return dsStyledResolver;
63
+ }) as (typeof baseStyledResolver)['attrs'],
64
+ withConfig: ((...args) => {
65
+ baseStyledResolver.withConfig(...args);
66
+ return dsStyledResolver;
67
+ }) as (typeof baseStyledResolver)['withConfig'],
68
+ };
69
+
70
+ return Object.assign(dsStyledResolver, styledAttributes);
71
+
72
+ ```
73
+
74
+ It looks scary. But let's break it down.
75
+
76
+ 1. We create a base styled resolver using the `baseStyled` function. This is just calling the ORIGINAL `styled` function from `styled-components` (we just changed its name when importing it).
77
+ Ultimately, this is the function that will be called when we use our styled component, since we are just building a wrapper of it.
78
+
79
+ 2. We create a new resolver, `dsStyledResolver`, that will be the one that will be returned by our wrapper. It has to receive as a first argument the styleArg, which is the style object or string that we pass to the styled component. The rest of the arguments are the interpolated values of the tagged template literal.
80
+ This function will do a lot of magic, but it will ultimately call the `baseStyledResolver` with the correct arguments.
81
+
82
+ 3. We create an object with two methods, `attrs` and `withConfig`, that will be used to augment the styled component. These methods are just calling the original methods of the `baseStyledResolver`, and then returning the `dsStyledResolver` itself. This is just to make sure that the styled component is augmented with the correct methods.
83
+
84
+ 4. We return an object that is the `dsStyledResolver` with the two methods we created. This is the final object that will be returned by our wrapper.
85
+
86
+ This is just a basic wrapper flow, with an extra step for the `attrs` and `withConfig` function. The `dsStyledResolver` is the one that will be used to create the styled component, and it will have all the magic we need to make it work with our design system.
87
+
88
+ Now let's see the `dsStyledResolver` function:
89
+
90
+ ```typescript
91
+ const expressionsWithDsStyledComponentId = dsStyledComponentTransform(expressions);
92
+ ```
93
+
94
+ Well, this function is needed for something we do later in the code, but the basic idea is to support this syntax:
95
+
96
+ ```typescript
97
+ const StyledChat = styled('div')`
98
+ ${StyledChatBubble} {
99
+ margin: 0;
100
+ }
101
+ `;
102
+ ```
103
+
104
+ Now that you understand how the tagged template literals work, you can see that we are interpolating a styled component inside another styled component. This is a common pattern in our codebase, and we need to support it.
105
+
106
+ Our styled-component, will have a `dsStyledComponentId` added to them, so we can reference them on this transformation.
107
+
108
+ The `dsStyledComponentTransform` function will transform the styled component into its `dsStyledComponentId` so we can use it on the interpolation.
109
+
110
+ So basically it will transform `StyledChatBubble`
111
+
112
+ ```typescript
113
+ `.${StyledChatBubble.dsStyledComponentId}`
114
+ ```
115
+
116
+ Notice the `.` before the interpolation. This is because we are interpolating a class name, and we need to add the `.` to make it a valid CSS selector.
117
+
118
+ ```typescript
119
+ const [styleArgWithMagic, expressionsWithMagic] = magicCssTransform(styleArg, expressionsWithDsStyledComponentId);
120
+ ```
121
+
122
+ This transformation allows our styled components to be MAGICAL. Meaning, we support things like this:
123
+
124
+ ```typescript
125
+ const StyledButton = styled('button')`
126
+ color: neutral-100;
127
+ `;
128
+ ```
129
+
130
+ Doing this is not so simple, but I will try to explain it in a simple way.
131
+
132
+ First we need to do a regex search to locate something that we can replace with a theme getter (for now we just support colors, but we can extend it to other things).
133
+
134
+ Then we need to replace that token with the corresponding value from the theme, using a function, so it's the same as this:
135
+
136
+ ```typescript
137
+ const StyledButton = styled('button')`
138
+ color: ${({theme}) => theme.colors.neutral[100]};
139
+ `;
140
+ ```
141
+
142
+ Now, let's analyze the styleArg vs expressions thing.
143
+
144
+ Before this transformation, styleArg was: `color: neutral 100;\n`, and no expressions.
145
+
146
+ After this transformation, styleArg is `["color: ", ";\n"]` and expressions is `[({theme}) => theme.colors.neutral[100]]`.
147
+
148
+ So, every time we find a token, we split the styleArg in two, and a function is added to the expressions array.
149
+
150
+ Let's see the next part of the code:
151
+
152
+ ```typescript
153
+ const expressionsWithThemeCoerced = coerceWithDefaultTheme(expressionsWithMagic);
154
+ ```
155
+
156
+ This transformation just adds a default theme in case no theme was provided on the application. So basically we just replace every function on the expressions array with a new function with the theme coerced.
157
+
158
+ ```
159
+ expresions = [f1, f2, f3]
160
+
161
+ -->
162
+
163
+ expressions = [({theme,...rest}) => f1({...rest, theme: theme ?? defaultTheme}), ...]
164
+ ```
165
+
166
+
167
+ Let's look at the next important part
168
+
169
+ ```typescript
170
+ if (componentName && componentSlot) {
171
+ expressionsWithThemeCoerced.push(genStyleOverridesExpression(componentName, componentSlot));
172
+ }
173
+
174
+ if (componentName && componentSlot === 'root') {
175
+ expressionsWithThemeCoerced.push(genVariantOverridesExpression(componentName));
176
+ }
177
+
178
+ const numOfExpressionsAdded = expressionsWithThemeCoerced.length - expressionsWithMagic.length;
179
+
180
+ const fixedStyleArg = fixStyleArg(styleArgWithMagic, numOfExpressionsAdded);
181
+ ```
182
+
183
+ This is where we add both style and variant overrides to the styled component. Here we access the theme object in search of the corresponding variant and style overrides, and we push them into the expressions array.
184
+
185
+ Notice that we are adding the expressions at the end of the array, so they will be the last ones to be evaluated, therefore they will override any previous styles.
186
+
187
+ It's also important to note that we need to fix the styleArg after adding the expressions, since we added new expressions, and we need to adjust the styleArg to match the new expressions array.
188
+
189
+
190
+ In the next part, we just generate some autocalculated values, like display name, data-testids, classNames, etc. And then, we just add them one by one by using the `attrs` function
191
+
192
+ ```typescript
193
+ const [displayName, attributes] = generateAutoCalculated(options);
194
+
195
+ const baseStyledResolverWithAttributes = attributes.reduce<typeof baseStyledResolver>(
196
+ (curStyledResolver, attributeFunc) => curStyledResolver.attrs(attributeFunc),
197
+ baseStyledResolver,
198
+ );
199
+ ```
200
+
201
+ Notice that we are using the `baseStyledResolver` here!!! We are adding the attributes to the original styled.
202
+ Now, we will call this styled with the styleArg and expression that we calculated before, and we will return the styled component.
203
+
204
+ ```typescript
205
+ const Component = baseStyledResolverWithAttributes(fixedStyleArg, ...expressionsWithThemeCoerced);
206
+ ```
207
+
208
+ So, wrapper ONLY augments the styleArg and expressions, and then calls the original styled function with the augmented arguments.
209
+
210
+ There is an extra piece of logic after this, which is creating a wrapper for this component that automatically merges refs, puts global attributes, etc.
211
+
212
+
213
+ ```typescript
214
+ const ComponentWithRefsMerged = (props: OwnerInterface & InnerRefInterface<C> & { 'data-testid'?: string }) => {
215
+ // we may or not have data-testid at this point, so we need to calculate it
216
+ const dataTestId = props['data-testid'] ?? (displayName !== null ? displayNameToKebabCase(displayName) : '');
217
+ // we also need to calculate dataDimsumSlot at this point
218
+ const dataDimsumSlot = displayName !== null ? displayNameToPropCase(displayName) : '';
219
+ // then we get the part props for this slot, and we cast it to use refs
220
+ const partProps = getPartProps({
221
+ ...props,
222
+ 'data-testid': dataTestId,
223
+ 'data-dimsum-slot': dataDimsumSlot,
224
+ }) as {
225
+ innerRef?: AnyStyledRef<HTMLElement>;
226
+ };
227
+
228
+ const mergedRef = useMemo(
229
+ () => mergeRefs(props.innerRef, partProps.innerRef),
230
+ [props.innerRef, partProps.innerRef],
231
+ );
232
+
233
+ const propsWithRefMerged = {
234
+ ...props,
235
+ ref: mergedRef,
236
+ innerRef: mergedRef,
237
+ } as React.ComponentProps<C>;
238
+
239
+ return <Component {...propsWithRefMerged} />;
240
+ };
241
+
242
+ ```
243
+
244
+ But it should be straightforward.
245
+
246
+ Last 2 lines of code are just adding some properties to the wrapper, so it can be used as a styled component.
247
+ We need to add the `dsStyledComponentId` and `toString` to the wrapper, so we can use it on the interpolation.
248
+
249
+ ```typescript
250
+ ComponentWithRefsMerged.dsStyledComponentId = Component.styledComponentId as string;
251
+ ComponentWithRefsMerged.toString = Component.toString;
252
+ ```
@@ -0,0 +1,252 @@
1
+ ### Internal Workings of the Styled-Component Wrapper
2
+
3
+ This document delves into the internal mechanics of our custom styled-component wrapper, providing insights for developers who need to understand or extend its functionality.
4
+
5
+ #### Overview
6
+ The wrapper is designed to extend the standard functionality of styled-components with additional features like automatic theme coercion, design system integration, and automatic attribute generation. It acts as a higher-order component (HOC) that wraps around the base styled component.
7
+
8
+ #### Understanding Tagged Template Literals
9
+ In standard styled-components usage, styled components are often defined using tagged template literals, a feature of ES6. For example:
10
+
11
+ ```typescript
12
+ const StyledButton = styled.button`
13
+ margin: ${({ theme }) => theme.space.xs};
14
+ `;
15
+ ```
16
+
17
+ This syntax is concise and readable, especially for defining component styles that directly leverage props and themes.
18
+
19
+ But, for the purpose of this document, you should understand the real way this gets "transpiled" to:
20
+
21
+ ```typescript
22
+ styled('button')(['margin: ', ';\n'], ({theme}) => theme.space.xs);
23
+ ```
24
+
25
+ So, `styled` is a function that takes a tag (in this case, `'button'`) and returns a function that takes any number of arguments. The first argument is an array of strings (the constant parts of the template literal), and the rest are the interpolated values (the dynamic parts of the template literal). Each interpolated value will be inserted into the array of strings at the corresponding index.
26
+
27
+ This is just for the most-used-case on our library though. Styled components also accept object instead of tagged template literals, and we have to handle that as well. So keep that in mind as well.
28
+
29
+ #### The idea of the Wrapper
30
+
31
+ The wrapper is designed to also receive as a parameter an options object, along with the tag. So the signature of the wrapper is:
32
+
33
+ ```typescript
34
+ const OurStyled = (tag: Tag, options: {name: string, slot: string}) => ReturnType<styled('tag')>
35
+ ```
36
+
37
+ These name and slot properties will be used for theming the styled component, for applying style and variant overrides from the design system, calculate data-testids, data-dimsum-slot and other attributes as well, ref-merging, etc.
38
+
39
+ #### Let's see the code
40
+
41
+ ```typescript
42
+ checkNamingConvention(componentName, componentSlot);
43
+ ```
44
+
45
+ This function checks if the `name` and `slot` properties adhere to our project's naming conventions. If they don't, it triggers a warning.
46
+
47
+ The correct format for componentName is `DSComponentName`, and for componentSlot is `component-slot`. This is important since other functionalities rely on these properties being correctly formatted.
48
+
49
+ Now, let's look at the overall body of the function
50
+
51
+ ```typescript
52
+ const baseStyledResolver = baseStyled(tag) as unknown as ThemedStyledFunction<C, Theme, {}, keyof any>;
53
+
54
+ const dsStyledResolver: ThemedStyledFunctionBase<C, Theme> = (styleArg, ...expressions) => {
55
+ ... some code that we will see later ...
56
+ };
57
+
58
+ // Here we use arrow functions to preserve the `this` context
59
+ const styledAttributes = {
60
+ attrs: ((...args) => {
61
+ baseStyledResolver.attrs(...args);
62
+ return dsStyledResolver;
63
+ }) as (typeof baseStyledResolver)['attrs'],
64
+ withConfig: ((...args) => {
65
+ baseStyledResolver.withConfig(...args);
66
+ return dsStyledResolver;
67
+ }) as (typeof baseStyledResolver)['withConfig'],
68
+ };
69
+
70
+ return Object.assign(dsStyledResolver, styledAttributes);
71
+
72
+ ```
73
+
74
+ It looks scary. But let's break it down.
75
+
76
+ 1. We create a base styled resolver using the `baseStyled` function. This is just calling the ORIGINAL `styled` function from `styled-components` (we just changed its name when importing it).
77
+ Ultimately, this is the function that will be called when we use our styled component, since we are just building a wrapper of it.
78
+
79
+ 2. We create a new resolver, `dsStyledResolver`, that will be the one that will be returned by our wrapper. It has to receive as a first argument the styleArg, which is the style object or string that we pass to the styled component. The rest of the arguments are the interpolated values of the tagged template literal.
80
+ This function will do a lot of magic, but it will ultimately call the `baseStyledResolver` with the correct arguments.
81
+
82
+ 3. We create an object with two methods, `attrs` and `withConfig`, that will be used to augment the styled component. These methods are just calling the original methods of the `baseStyledResolver`, and then returning the `dsStyledResolver` itself. This is just to make sure that the styled component is augmented with the correct methods.
83
+
84
+ 4. We return an object that is the `dsStyledResolver` with the two methods we created. This is the final object that will be returned by our wrapper.
85
+
86
+ This is just a basic wrapper flow, with an extra step for the `attrs` and `withConfig` function. The `dsStyledResolver` is the one that will be used to create the styled component, and it will have all the magic we need to make it work with our design system.
87
+
88
+ Now let's see the `dsStyledResolver` function:
89
+
90
+ ```typescript
91
+ const expressionsWithDsStyledComponentId = dsStyledComponentTransform(expressions);
92
+ ```
93
+
94
+ Well, this function is needed for something we do later in the code, but the basic idea is to support this syntax:
95
+
96
+ ```typescript
97
+ const StyledChat = styled('div')`
98
+ ${StyledChatBubble} {
99
+ margin: 0;
100
+ }
101
+ `;
102
+ ```
103
+
104
+ Now that you understand how the tagged template literals work, you can see that we are interpolating a styled component inside another styled component. This is a common pattern in our codebase, and we need to support it.
105
+
106
+ Our styled-component, will have a `dsStyledComponentId` added to them, so we can reference them on this transformation.
107
+
108
+ The `dsStyledComponentTransform` function will transform the styled component into its `dsStyledComponentId` so we can use it on the interpolation.
109
+
110
+ So basically it will transform `StyledChatBubble`
111
+
112
+ ```typescript
113
+ `.${StyledChatBubble.dsStyledComponentId}`
114
+ ```
115
+
116
+ Notice the `.` before the interpolation. This is because we are interpolating a class name, and we need to add the `.` to make it a valid CSS selector.
117
+
118
+ ```typescript
119
+ const [styleArgWithMagic, expressionsWithMagic] = magicCssTransform(styleArg, expressionsWithDsStyledComponentId);
120
+ ```
121
+
122
+ This transformation allows our styled components to be MAGICAL. Meaning, we support things like this:
123
+
124
+ ```typescript
125
+ const StyledButton = styled('button')`
126
+ color: neutral-100;
127
+ `;
128
+ ```
129
+
130
+ Doing this is not so simple, but I will try to explain it in a simple way.
131
+
132
+ First we need to do a regex search to locate something that we can replace with a theme getter (for now we just support colors, but we can extend it to other things).
133
+
134
+ Then we need to replace that token with the corresponding value from the theme, using a function, so it's the same as this:
135
+
136
+ ```typescript
137
+ const StyledButton = styled('button')`
138
+ color: ${({theme}) => theme.colors.neutral[100]};
139
+ `;
140
+ ```
141
+
142
+ Now, let's analyze the styleArg vs expressions thing.
143
+
144
+ Before this transformation, styleArg was: `color: neutral 100;\n`, and no expressions.
145
+
146
+ After this transformation, styleArg is `["color: ", ";\n"]` and expressions is `[({theme}) => theme.colors.neutral[100]]`.
147
+
148
+ So, every time we find a token, we split the styleArg in two, and a function is added to the expressions array.
149
+
150
+ Let's see the next part of the code:
151
+
152
+ ```typescript
153
+ const expressionsWithThemeCoerced = coerceWithDefaultTheme(expressionsWithMagic);
154
+ ```
155
+
156
+ This transformation just adds a default theme in case no theme was provided on the application. So basically we just replace every function on the expressions array with a new function with the theme coerced.
157
+
158
+ ```
159
+ expresions = [f1, f2, f3]
160
+
161
+ -->
162
+
163
+ expressions = [({theme,...rest}) => f1({...rest, theme: theme ?? defaultTheme}), ...]
164
+ ```
165
+
166
+
167
+ Let's look at the next important part
168
+
169
+ ```typescript
170
+ if (componentName && componentSlot) {
171
+ expressionsWithThemeCoerced.push(genStyleOverridesExpression(componentName, componentSlot));
172
+ }
173
+
174
+ if (componentName && componentSlot === 'root') {
175
+ expressionsWithThemeCoerced.push(genVariantOverridesExpression(componentName));
176
+ }
177
+
178
+ const numOfExpressionsAdded = expressionsWithThemeCoerced.length - expressionsWithMagic.length;
179
+
180
+ const fixedStyleArg = fixStyleArg(styleArgWithMagic, numOfExpressionsAdded);
181
+ ```
182
+
183
+ This is where we add both style and variant overrides to the styled component. Here we access the theme object in search of the corresponding variant and style overrides, and we push them into the expressions array.
184
+
185
+ Notice that we are adding the expressions at the end of the array, so they will be the last ones to be evaluated, therefore they will override any previous styles.
186
+
187
+ It's also important to note that we need to fix the styleArg after adding the expressions, since we added new expressions, and we need to adjust the styleArg to match the new expressions array.
188
+
189
+
190
+ In the next part, we just generate some autocalculated values, like display name, data-testids, classNames, etc. And then, we just add them one by one by using the `attrs` function
191
+
192
+ ```typescript
193
+ const [displayName, attributes] = generateAutoCalculated(options);
194
+
195
+ const baseStyledResolverWithAttributes = attributes.reduce<typeof baseStyledResolver>(
196
+ (curStyledResolver, attributeFunc) => curStyledResolver.attrs(attributeFunc),
197
+ baseStyledResolver,
198
+ );
199
+ ```
200
+
201
+ Notice that we are using the `baseStyledResolver` here!!! We are adding the attributes to the original styled.
202
+ Now, we will call this styled with the styleArg and expression that we calculated before, and we will return the styled component.
203
+
204
+ ```typescript
205
+ const Component = baseStyledResolverWithAttributes(fixedStyleArg, ...expressionsWithThemeCoerced);
206
+ ```
207
+
208
+ So, wrapper ONLY augments the styleArg and expressions, and then calls the original styled function with the augmented arguments.
209
+
210
+ There is an extra piece of logic after this, which is creating a wrapper for this component that automatically merges refs, puts global attributes, etc.
211
+
212
+
213
+ ```typescript
214
+ const ComponentWithRefsMerged = (props: OwnerInterface & InnerRefInterface<C> & { 'data-testid'?: string }) => {
215
+ // we may or not have data-testid at this point, so we need to calculate it
216
+ const dataTestId = props['data-testid'] ?? (displayName !== null ? displayNameToKebabCase(displayName) : '');
217
+ // we also need to calculate dataDimsumSlot at this point
218
+ const dataDimsumSlot = displayName !== null ? displayNameToPropCase(displayName) : '';
219
+ // then we get the part props for this slot, and we cast it to use refs
220
+ const partProps = getPartProps({
221
+ ...props,
222
+ 'data-testid': dataTestId,
223
+ 'data-dimsum-slot': dataDimsumSlot,
224
+ }) as {
225
+ innerRef?: AnyStyledRef<HTMLElement>;
226
+ };
227
+
228
+ const mergedRef = useMemo(
229
+ () => mergeRefs(props.innerRef, partProps.innerRef),
230
+ [props.innerRef, partProps.innerRef],
231
+ );
232
+
233
+ const propsWithRefMerged = {
234
+ ...props,
235
+ ref: mergedRef,
236
+ innerRef: mergedRef,
237
+ } as React.ComponentProps<C>;
238
+
239
+ return <Component {...propsWithRefMerged} />;
240
+ };
241
+
242
+ ```
243
+
244
+ But it should be straightforward.
245
+
246
+ Last 2 lines of code are just adding some properties to the wrapper, so it can be used as a styled component.
247
+ We need to add the `dsStyledComponentId` and `toString` to the wrapper, so we can use it on the interpolation.
248
+
249
+ ```typescript
250
+ ComponentWithRefsMerged.dsStyledComponentId = Component.styledComponentId as string;
251
+ ComponentWithRefsMerged.toString = Component.toString;
252
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elliemae/ds-system",
3
- "version": "3.33.0-next.0",
3
+ "version": "3.33.0-next.2",
4
4
  "license": "MIT",
5
5
  "description": "ICE MT - Dimsum - System",
6
6
  "files": [
@@ -100,7 +100,7 @@
100
100
  "@elliemae/pui-cli": "~9.0.0-next.31",
101
101
  "@elliemae/pui-theme": "~2.7.0",
102
102
  "styled-components": "~5.3.9",
103
- "@elliemae/ds-monorepo-devops": "3.33.0-next.0"
103
+ "@elliemae/ds-monorepo-devops": "3.33.0-next.2"
104
104
  },
105
105
  "peerDependencies": {
106
106
  "@elliemae/pui-theme": "^2.7.0",