@khanacademy/wonder-blocks-card 0.0.0-PR2816-20251007213621 → 0.0.0-PR2816-20251007220942

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.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @khanacademy/wonder-blocks-card
2
2
 
3
- ## 0.0.0-PR2816-20251007213621
3
+ ## 0.0.0-PR2816-20251007220942
4
4
 
5
5
  ### Minor Changes
6
6
 
package/dist/es/index.js CHANGED
@@ -9,6 +9,6 @@ import { focusStyles } from '@khanacademy/wonder-blocks-styles';
9
9
 
10
10
  const DismissButton=props=>{const{onClick,style,testId}=props;return jsx(IconButton,{icon:xIcon,"aria-label":props["aria-label"]||"Close",onClick:onClick,kind:"tertiary",actionType:"neutral",style:[componentStyles.root,style],testId:testId})};const componentStyles=StyleSheet.create({root:{position:"absolute",insetInlineEnd:sizing.size_080,top:sizing.size_080,zIndex:1,":focus":focusStyles.focus[":focus-visible"]}});
11
11
 
12
- const Card=React.forwardRef(function Card(props,ref){const{styles,labels,tag,testId,background="base-default",borderRadius="small",paddingSize="small",elevation="none",children,onDismiss,inert}=props;const componentStyles=getComponentStyles({background,borderRadius,paddingSize,elevation});return jsxs(View,{"aria-label":labels?.cardAriaLabel,style:[componentStyles.root,styles?.root],ref:ref,tag:tag,testId:testId,inert:inert?"":undefined,children:[onDismiss?jsx(DismissButton,{"aria-label":labels?.dismissButtonAriaLabel||"Close",onClick:e=>onDismiss?.(e)}):null,children]})});const getComponentStyles=({background,borderRadius,paddingSize,elevation})=>{const styleMap={backgroundColor:{"base-subtle":semanticColor.core.background.base.subtle,"base-default":semanticColor.core.background.base.default},borderRadius:{small:border.radius.radius_080,medium:border.radius.radius_120},padding:{none:sizing.size_0,small:sizing.size_160,medium:sizing.size_240},elevation:{none:"none",low:boxShadow.low}};const isBackgroundColorStyle=background==="base-subtle"||background==="base-default";return StyleSheet.create({root:{...isBackgroundColorStyle&&{backgroundColor:styleMap.backgroundColor[background]},...!isBackgroundColorStyle&&{background:`url(${background})`,backgroundSize:"cover"},borderColor:semanticColor.core.border.neutral.subtle,borderStyle:"solid",borderRadius:borderRadius&&styleMap.borderRadius[borderRadius],borderWidth:border.width.thin,boxShadow:elevation&&styleMap.elevation[elevation],padding:paddingSize&&styleMap.padding[paddingSize],minInlineSize:sizing.size_280,position:"relative"}})};
12
+ const Card=React.forwardRef(function Card(props,ref){const{styles,labels,tag,testId,background="base-default",borderRadius="small",paddingSize="small",elevation="none",children,onDismiss,inert}=props;const componentStyles=getComponentStyles({background,borderRadius,paddingSize,elevation});return jsxs(View,{"aria-label":labels?.cardAriaLabel,style:[componentStyles.root,styles?.root],ref:ref,tag:tag,testId:testId,inert:inert?"":undefined,children:[onDismiss?jsx(DismissButton,{"aria-label":labels?.dismissButtonAriaLabel||"Close",onClick:e=>onDismiss?.(e)}):null,children]})});const styleMap={backgroundColor:{"base-subtle":semanticColor.core.background.base.subtle,"base-default":semanticColor.core.background.base.default},borderRadius:{small:border.radius.radius_080,medium:border.radius.radius_120},padding:{none:sizing.size_0,small:sizing.size_160,medium:sizing.size_240},elevation:{none:"none",low:boxShadow.low}};const dynamicStyles={};const getStyleKey=({background,borderRadius,paddingSize,elevation})=>{return `${background||"default"}-${borderRadius||"small"}-${paddingSize||"small"}-${elevation||"none"}`};const getComponentStyles=props=>{const styleKey=getStyleKey(props);if(dynamicStyles[styleKey]){return dynamicStyles[styleKey]}const{background,borderRadius,paddingSize,elevation}=props;const isBackgroundColorStyle=background==="base-subtle"||background==="base-default";const newStyles=StyleSheet.create({root:{...isBackgroundColorStyle&&{backgroundColor:styleMap.backgroundColor[background]},...!isBackgroundColorStyle&&background&&{background:`url(${background})`,backgroundSize:"cover"},borderColor:semanticColor.core.border.neutral.subtle,borderStyle:"solid",borderWidth:border.width.thin,minInlineSize:sizing.size_280,position:"relative",borderRadius:borderRadius&&styleMap.borderRadius[borderRadius],boxShadow:elevation&&styleMap.elevation[elevation],padding:paddingSize&&styleMap.padding[paddingSize]}});dynamicStyles[styleKey]=newStyles;return newStyles};
13
13
 
14
14
  export { Card };
package/dist/index.js CHANGED
@@ -37,6 +37,6 @@ var IconButton__default = /*#__PURE__*/_interopDefaultLegacy(IconButton);
37
37
 
38
38
  const DismissButton=props=>{const{onClick,style,testId}=props;return jsxRuntime.jsx(IconButton__default["default"],{icon:xIcon__default["default"],"aria-label":props["aria-label"]||"Close",onClick:onClick,kind:"tertiary",actionType:"neutral",style:[componentStyles.root,style],testId:testId})};const componentStyles=aphrodite.StyleSheet.create({root:{position:"absolute",insetInlineEnd:wonderBlocksTokens.sizing.size_080,top:wonderBlocksTokens.sizing.size_080,zIndex:1,":focus":wonderBlocksStyles.focusStyles.focus[":focus-visible"]}});
39
39
 
40
- const Card=React__namespace.forwardRef(function Card(props,ref){const{styles,labels,tag,testId,background="base-default",borderRadius="small",paddingSize="small",elevation="none",children,onDismiss,inert}=props;const componentStyles=getComponentStyles({background,borderRadius,paddingSize,elevation});return jsxRuntime.jsxs(wonderBlocksCore.View,{"aria-label":labels?.cardAriaLabel,style:[componentStyles.root,styles?.root],ref:ref,tag:tag,testId:testId,inert:inert?"":undefined,children:[onDismiss?jsxRuntime.jsx(DismissButton,{"aria-label":labels?.dismissButtonAriaLabel||"Close",onClick:e=>onDismiss?.(e)}):null,children]})});const getComponentStyles=({background,borderRadius,paddingSize,elevation})=>{const styleMap={backgroundColor:{"base-subtle":wonderBlocksTokens.semanticColor.core.background.base.subtle,"base-default":wonderBlocksTokens.semanticColor.core.background.base.default},borderRadius:{small:wonderBlocksTokens.border.radius.radius_080,medium:wonderBlocksTokens.border.radius.radius_120},padding:{none:wonderBlocksTokens.sizing.size_0,small:wonderBlocksTokens.sizing.size_160,medium:wonderBlocksTokens.sizing.size_240},elevation:{none:"none",low:wonderBlocksTokens.boxShadow.low}};const isBackgroundColorStyle=background==="base-subtle"||background==="base-default";return aphrodite.StyleSheet.create({root:{...isBackgroundColorStyle&&{backgroundColor:styleMap.backgroundColor[background]},...!isBackgroundColorStyle&&{background:`url(${background})`,backgroundSize:"cover"},borderColor:wonderBlocksTokens.semanticColor.core.border.neutral.subtle,borderStyle:"solid",borderRadius:borderRadius&&styleMap.borderRadius[borderRadius],borderWidth:wonderBlocksTokens.border.width.thin,boxShadow:elevation&&styleMap.elevation[elevation],padding:paddingSize&&styleMap.padding[paddingSize],minInlineSize:wonderBlocksTokens.sizing.size_280,position:"relative"}})};
40
+ const Card=React__namespace.forwardRef(function Card(props,ref){const{styles,labels,tag,testId,background="base-default",borderRadius="small",paddingSize="small",elevation="none",children,onDismiss,inert}=props;const componentStyles=getComponentStyles({background,borderRadius,paddingSize,elevation});return jsxRuntime.jsxs(wonderBlocksCore.View,{"aria-label":labels?.cardAriaLabel,style:[componentStyles.root,styles?.root],ref:ref,tag:tag,testId:testId,inert:inert?"":undefined,children:[onDismiss?jsxRuntime.jsx(DismissButton,{"aria-label":labels?.dismissButtonAriaLabel||"Close",onClick:e=>onDismiss?.(e)}):null,children]})});const styleMap={backgroundColor:{"base-subtle":wonderBlocksTokens.semanticColor.core.background.base.subtle,"base-default":wonderBlocksTokens.semanticColor.core.background.base.default},borderRadius:{small:wonderBlocksTokens.border.radius.radius_080,medium:wonderBlocksTokens.border.radius.radius_120},padding:{none:wonderBlocksTokens.sizing.size_0,small:wonderBlocksTokens.sizing.size_160,medium:wonderBlocksTokens.sizing.size_240},elevation:{none:"none",low:wonderBlocksTokens.boxShadow.low}};const dynamicStyles={};const getStyleKey=({background,borderRadius,paddingSize,elevation})=>{return `${background||"default"}-${borderRadius||"small"}-${paddingSize||"small"}-${elevation||"none"}`};const getComponentStyles=props=>{const styleKey=getStyleKey(props);if(dynamicStyles[styleKey]){return dynamicStyles[styleKey]}const{background,borderRadius,paddingSize,elevation}=props;const isBackgroundColorStyle=background==="base-subtle"||background==="base-default";const newStyles=aphrodite.StyleSheet.create({root:{...isBackgroundColorStyle&&{backgroundColor:styleMap.backgroundColor[background]},...!isBackgroundColorStyle&&background&&{background:`url(${background})`,backgroundSize:"cover"},borderColor:wonderBlocksTokens.semanticColor.core.border.neutral.subtle,borderStyle:"solid",borderWidth:wonderBlocksTokens.border.width.thin,minInlineSize:wonderBlocksTokens.sizing.size_280,position:"relative",borderRadius:borderRadius&&styleMap.borderRadius[borderRadius],boxShadow:elevation&&styleMap.elevation[elevation],padding:paddingSize&&styleMap.padding[paddingSize]}});dynamicStyles[styleKey]=newStyles;return newStyles};
41
41
 
42
42
  exports.Card = Card;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-card",
3
- "version": "0.0.0-PR2816-20251007213621",
3
+ "version": "0.0.0-PR2816-20251007220942",
4
4
  "design": "v1",
5
5
  "description": "Card component for Wonder Blocks.",
6
6
  "main": "dist/index.js",
@@ -13,8 +13,8 @@
13
13
  "access": "public"
14
14
  },
15
15
  "dependencies": {
16
- "@khanacademy/wonder-blocks-icon-button": "10.5.2",
17
16
  "@khanacademy/wonder-blocks-core": "12.4.0",
17
+ "@khanacademy/wonder-blocks-icon-button": "10.5.2",
18
18
  "@khanacademy/wonder-blocks-tokens": "14.0.0"
19
19
  },
20
20
  "peerDependencies": {
@@ -1,6 +1,12 @@
1
1
  import * as React from "react";
2
2
  import {render, screen} from "@testing-library/react";
3
3
  import {userEvent} from "@testing-library/user-event";
4
+ import {
5
+ boxShadow,
6
+ border,
7
+ semanticColor,
8
+ sizing,
9
+ } from "@khanacademy/wonder-blocks-tokens";
4
10
 
5
11
  import Card from "../../components/card";
6
12
 
@@ -191,4 +197,180 @@ describe("Card", () => {
191
197
  expect(secondChild).toBeInTheDocument();
192
198
  });
193
199
  });
200
+
201
+ describe("Style application", () => {
202
+ it("should apply default styles", () => {
203
+ // Arrange
204
+ const testId = "test-card";
205
+
206
+ // Act
207
+ render(<Card testId={testId}>Content</Card>);
208
+ const card = screen.getByTestId(testId);
209
+
210
+ // Assert
211
+ expect(card).toHaveStyle({
212
+ position: "relative",
213
+ borderStyle: "solid",
214
+ });
215
+ });
216
+
217
+ it("should apply base-subtle background", () => {
218
+ // Arrange
219
+ const testId = "test-card";
220
+
221
+ // Act
222
+ render(
223
+ <Card testId={testId} background="base-subtle">
224
+ Content
225
+ </Card>,
226
+ );
227
+ const card = screen.getByTestId(testId);
228
+
229
+ // Assert
230
+ expect(card).toHaveStyle({
231
+ backgroundColor: semanticColor.core.background.base.subtle,
232
+ });
233
+ });
234
+
235
+ it("should apply medium border radius", () => {
236
+ // Arrange
237
+ const testId = "test-card";
238
+
239
+ // Act
240
+ render(
241
+ <Card testId={testId} borderRadius="medium">
242
+ Content
243
+ </Card>,
244
+ );
245
+ const card = screen.getByTestId(testId);
246
+
247
+ // Assert
248
+ expect(card).toHaveStyle({
249
+ borderRadius: border.radius.radius_120,
250
+ });
251
+ });
252
+
253
+ it("should apply medium padding", () => {
254
+ // Arrange
255
+ const testId = "test-card";
256
+
257
+ // Act
258
+ render(
259
+ <Card testId={testId} paddingSize="medium">
260
+ Content
261
+ </Card>,
262
+ );
263
+ const card = screen.getByTestId(testId);
264
+
265
+ // Assert
266
+ expect(card).toHaveStyle({
267
+ padding: sizing.size_240,
268
+ });
269
+ });
270
+
271
+ it("should apply low elevation", () => {
272
+ // Arrange
273
+ const testId = "test-card";
274
+
275
+ // Act
276
+ render(
277
+ <Card testId={testId} elevation="low">
278
+ Content
279
+ </Card>,
280
+ );
281
+ const card = screen.getByTestId(testId);
282
+
283
+ // Assert
284
+ expect(card).toHaveStyle({
285
+ boxShadow: boxShadow.low,
286
+ });
287
+ });
288
+
289
+ it("should apply image background", () => {
290
+ // Arrange
291
+ const testId = "test-card";
292
+ const testImage = Image;
293
+
294
+ // Act
295
+ render(
296
+ <Card testId={testId} background={testImage}>
297
+ Content
298
+ </Card>,
299
+ );
300
+ const card = screen.getByTestId(testId);
301
+
302
+ // Assert
303
+ expect(card).toHaveStyle({
304
+ backgroundSize: "cover",
305
+ });
306
+ });
307
+ });
308
+
309
+ describe("Style application", () => {
310
+ it("should apply custom margin style", () => {
311
+ // Arrange
312
+ const testId = "test-card";
313
+ const customStyle = {marginTop: "10px"};
314
+
315
+ // Act
316
+ render(
317
+ <Card testId={testId} styles={{root: customStyle}}>
318
+ Content
319
+ </Card>,
320
+ );
321
+
322
+ // Assert
323
+ expect(screen.getByTestId(testId)).toHaveStyle(customStyle);
324
+ });
325
+
326
+ it("should apply custom padding style", () => {
327
+ // Arrange
328
+ const testId = "test-card";
329
+ const customStyle = {padding: "20px"};
330
+
331
+ // Act
332
+ render(
333
+ <Card testId={testId} styles={{root: customStyle}}>
334
+ Content
335
+ </Card>,
336
+ );
337
+
338
+ // Assert
339
+ expect(screen.getByTestId(testId)).toHaveStyle(customStyle);
340
+ });
341
+
342
+ it("should apply custom background color", () => {
343
+ // Arrange
344
+ const testId = "test-card";
345
+ const customStyle = {backgroundColor: "rgb(255, 0, 0)"};
346
+
347
+ // Act
348
+ render(
349
+ <Card testId={testId} styles={{root: customStyle}}>
350
+ Content
351
+ </Card>,
352
+ );
353
+
354
+ // Assert
355
+ expect(screen.getByTestId(testId)).toHaveStyle(customStyle);
356
+ });
357
+
358
+ it("should maintain default styles with custom styles", () => {
359
+ // Arrange
360
+ const testId = "test-card";
361
+ const customStyle = {marginTop: "10px"};
362
+
363
+ // Act
364
+ render(
365
+ <Card testId={testId} styles={{root: customStyle}}>
366
+ Content
367
+ </Card>,
368
+ );
369
+
370
+ // Assert
371
+ const element = screen.getByTestId(testId);
372
+ expect(element).toHaveStyle(customStyle);
373
+ expect(element).toHaveStyle({position: "relative"}); // A default style
374
+ });
375
+ });
194
376
  });
@@ -191,56 +191,87 @@ const Card = React.forwardRef(function Card(
191
191
  );
192
192
  });
193
193
 
194
- const getComponentStyles = ({
194
+ // Map prop values to tokens
195
+ const styleMap = {
196
+ backgroundColor: {
197
+ "base-subtle": semanticColor.core.background.base.subtle,
198
+ "base-default": semanticColor.core.background.base.default,
199
+ },
200
+ borderRadius: {
201
+ small: border.radius.radius_080,
202
+ medium: border.radius.radius_120,
203
+ },
204
+ padding: {
205
+ none: sizing.size_0,
206
+ small: sizing.size_160,
207
+ medium: sizing.size_240,
208
+ },
209
+ elevation: {
210
+ none: "none",
211
+ low: boxShadow.low,
212
+ },
213
+ } as const;
214
+
215
+ // Cache for dynamically generated styles
216
+ const dynamicStyles: Record<string, any> = {};
217
+
218
+ /**
219
+ * Generates a unique key for caching styles based on prop combinations
220
+ */
221
+ const getStyleKey = ({
195
222
  background,
196
223
  borderRadius,
197
224
  paddingSize,
198
225
  elevation,
199
- }: StyleProps) => {
200
- // Map prop values to tokens
201
- const styleMap = {
202
- backgroundColor: {
203
- "base-subtle": semanticColor.core.background.base.subtle,
204
- "base-default": semanticColor.core.background.base.default,
205
- },
206
- borderRadius: {
207
- small: border.radius.radius_080,
208
- medium: border.radius.radius_120,
209
- },
210
- padding: {
211
- none: sizing.size_0,
212
- small: sizing.size_160,
213
- medium: sizing.size_240,
214
- },
215
- elevation: {
216
- none: "none",
217
- low: boxShadow.low,
218
- },
219
- } as const;
226
+ }: StyleProps): string => {
227
+ return `${background || "default"}-${borderRadius || "small"}-${
228
+ paddingSize || "small"
229
+ }-${elevation || "none"}`;
230
+ };
231
+
232
+ /**
233
+ * Generates the component styles with caching for better performance
234
+ */
235
+ const getComponentStyles = (props: StyleProps) => {
236
+ const styleKey = getStyleKey(props);
237
+ // Return cached styles if they exist
238
+ if (dynamicStyles[styleKey]) {
239
+ return dynamicStyles[styleKey];
240
+ }
220
241
 
242
+ const {background, borderRadius, paddingSize, elevation} = props;
221
243
  const isBackgroundColorStyle =
222
244
  background === "base-subtle" || background === "base-default";
223
245
 
224
- return StyleSheet.create({
246
+ // Generate new styles
247
+ const newStyles = StyleSheet.create({
225
248
  root: {
249
+ // Background styles
226
250
  ...(isBackgroundColorStyle && {
227
251
  backgroundColor: styleMap.backgroundColor[background],
228
252
  }),
229
- // provide background image styles for non-color background values
230
- ...(!isBackgroundColorStyle && {
231
- background: `url(${background})`,
232
- backgroundSize: "cover",
233
- }),
253
+ // Background image styles
254
+ ...(!isBackgroundColorStyle &&
255
+ background && {
256
+ background: `url(${background})`,
257
+ backgroundSize: "cover",
258
+ }),
259
+ // Common styles
234
260
  borderColor: semanticColor.core.border.neutral.subtle,
235
261
  borderStyle: "solid",
236
- borderRadius: borderRadius && styleMap.borderRadius[borderRadius],
237
262
  borderWidth: border.width.thin,
238
- boxShadow: elevation && styleMap.elevation[elevation],
239
- padding: paddingSize && styleMap.padding[paddingSize],
240
263
  minInlineSize: sizing.size_280,
241
264
  position: "relative",
265
+ // Optional styles based on props
266
+ borderRadius: borderRadius && styleMap.borderRadius[borderRadius],
267
+ boxShadow: elevation && styleMap.elevation[elevation],
268
+ padding: paddingSize && styleMap.padding[paddingSize],
242
269
  },
243
270
  });
271
+
272
+ // Cache the styles
273
+ dynamicStyles[styleKey] = newStyles;
274
+ return newStyles;
244
275
  };
245
276
 
246
277
  export default Card;