@khanacademy/wonder-blocks-card 0.0.0-PR2799-20250930173028 → 0.0.0-PR2799-20251001200207
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 +6 -2
- package/dist/es/index.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/__tests__/components/card.typestest.tsx +150 -0
- package/src/components/card.tsx +19 -27
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-card
|
|
2
2
|
|
|
3
|
-
## 0.0.0-PR2799-
|
|
3
|
+
## 0.0.0-PR2799-20251001200207
|
|
4
4
|
|
|
5
5
|
### Minor Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- 8d3fcc7: Add new Card component
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- @khanacademy/wonder-blocks-core@12.4.0
|
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,
|
|
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}};return StyleSheet.create({root:{backgroundColor:background&&styleMap.backgroundColor[background],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"}})};
|
|
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,
|
|
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}};return aphrodite.StyleSheet.create({root:{backgroundColor:background&&styleMap.backgroundColor[background],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"}})};
|
|
41
41
|
|
|
42
42
|
exports.Card = Card;
|
package/package.json
CHANGED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import Card from "../../components/card";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Basic Card usage
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
<Card>Hello, world!</Card>;
|
|
10
|
+
|
|
11
|
+
<Card>
|
|
12
|
+
<div>Some content</div>
|
|
13
|
+
</Card>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Card with all style props
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
<Card
|
|
20
|
+
background="base-default"
|
|
21
|
+
borderRadius="small"
|
|
22
|
+
paddingSize="small"
|
|
23
|
+
elevation="none"
|
|
24
|
+
>
|
|
25
|
+
Content
|
|
26
|
+
</Card>;
|
|
27
|
+
|
|
28
|
+
<Card
|
|
29
|
+
background="base-subtle"
|
|
30
|
+
borderRadius="medium"
|
|
31
|
+
paddingSize="medium"
|
|
32
|
+
elevation="low"
|
|
33
|
+
>
|
|
34
|
+
Content
|
|
35
|
+
</Card>;
|
|
36
|
+
|
|
37
|
+
<Card paddingSize="none">Content</Card>;
|
|
38
|
+
|
|
39
|
+
// @ts-expect-error - invalid background value
|
|
40
|
+
<Card background="invalid-background">Content</Card>;
|
|
41
|
+
|
|
42
|
+
// @ts-expect-error - invalid borderRadius value
|
|
43
|
+
<Card borderRadius="invalid-radius">Content</Card>;
|
|
44
|
+
|
|
45
|
+
// @ts-expect-error - invalid paddingSize value
|
|
46
|
+
<Card paddingSize="invalid-padding">Content</Card>;
|
|
47
|
+
|
|
48
|
+
// @ts-expect-error - invalid elevation value
|
|
49
|
+
<Card elevation="invalid-elevation">Content</Card>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Card with dismiss functionality
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
<Card onDismiss={() => {}} labels={{dismissButtonAriaLabel: "Close card"}}>
|
|
56
|
+
Content
|
|
57
|
+
</Card>;
|
|
58
|
+
|
|
59
|
+
// @ts-expect-error - onDismiss requires dismissButtonAriaLabel
|
|
60
|
+
<Card onDismiss={() => {}}>Content</Card>;
|
|
61
|
+
|
|
62
|
+
// @ts-expect-error - onDismiss requires dismissButtonAriaLabel
|
|
63
|
+
<Card onDismiss={() => {}} labels={{}}>
|
|
64
|
+
Content
|
|
65
|
+
</Card>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Card with different HTML tags
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
<Card tag="div">Content</Card>;
|
|
72
|
+
|
|
73
|
+
<Card tag="section" labels={{cardAriaLabel: "Card section"}}>
|
|
74
|
+
Content
|
|
75
|
+
</Card>;
|
|
76
|
+
|
|
77
|
+
<Card tag="figure" labels={{cardAriaLabel: "Card figure"}}>
|
|
78
|
+
Content
|
|
79
|
+
</Card>;
|
|
80
|
+
|
|
81
|
+
// @ts-expect-error - section tag requires cardAriaLabel
|
|
82
|
+
<Card tag="section">Content</Card>;
|
|
83
|
+
|
|
84
|
+
// @ts-expect-error - figure tag requires cardAriaLabel
|
|
85
|
+
<Card tag="figure">Content</Card>;
|
|
86
|
+
|
|
87
|
+
// @ts-expect-error - section tag requires cardAriaLabel in labels
|
|
88
|
+
<Card tag="section" labels={{}}>
|
|
89
|
+
Content
|
|
90
|
+
</Card>;
|
|
91
|
+
|
|
92
|
+
// @ts-expect-error - figure tag requires cardAriaLabel in labels
|
|
93
|
+
<Card tag="figure" labels={{}}>
|
|
94
|
+
Content
|
|
95
|
+
</Card>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Card with additional props
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
<Card testId="my-card">Content</Card>;
|
|
102
|
+
|
|
103
|
+
<Card inert>Content</Card>;
|
|
104
|
+
|
|
105
|
+
<Card ref={React.createRef<HTMLDivElement>()}>Content</Card>;
|
|
106
|
+
|
|
107
|
+
<Card styles={{root: {width: 200}, dismissButton: {position: "absolute"}}}>
|
|
108
|
+
Content
|
|
109
|
+
</Card>;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Card with dismiss and section tag (complex case)
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
<Card
|
|
116
|
+
tag="section"
|
|
117
|
+
onDismiss={() => {}}
|
|
118
|
+
labels={{
|
|
119
|
+
cardAriaLabel: "Card section",
|
|
120
|
+
dismissButtonAriaLabel: "Close card",
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
Content
|
|
124
|
+
</Card>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Card with all props
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
<Card
|
|
131
|
+
background="base-subtle"
|
|
132
|
+
borderRadius="medium"
|
|
133
|
+
paddingSize="medium"
|
|
134
|
+
elevation="low"
|
|
135
|
+
tag="figure"
|
|
136
|
+
onDismiss={() => {}}
|
|
137
|
+
labels={{
|
|
138
|
+
cardAriaLabel: "Card figure",
|
|
139
|
+
dismissButtonAriaLabel: "Close card",
|
|
140
|
+
}}
|
|
141
|
+
testId="complex-card"
|
|
142
|
+
inert
|
|
143
|
+
styles={{
|
|
144
|
+
root: {width: 300},
|
|
145
|
+
dismissButton: {top: 10, right: 10},
|
|
146
|
+
}}
|
|
147
|
+
ref={React.createRef<HTMLElement>()}
|
|
148
|
+
>
|
|
149
|
+
<div>Complex content</div>
|
|
150
|
+
</Card>;
|
package/src/components/card.tsx
CHANGED
|
@@ -40,7 +40,7 @@ type BaseCardProps = {
|
|
|
40
40
|
* The test ID used to locate this component in automated tests.
|
|
41
41
|
*/
|
|
42
42
|
testId?: string;
|
|
43
|
-
} &
|
|
43
|
+
} & StyleProps;
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* A callback function to handle dismissing the card. When this prop is present,
|
|
@@ -73,21 +73,16 @@ type TagProps =
|
|
|
73
73
|
labels?: Record<string, any>;
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
* Combined props - these two requirements work independently
|
|
78
|
-
*/
|
|
79
|
-
type ConditionalProps = BaseCardProps & TagProps & DismissProps;
|
|
80
|
-
|
|
81
|
-
type StyleOnlyProps = {
|
|
76
|
+
type StyleProps = {
|
|
82
77
|
/**
|
|
83
|
-
* The background
|
|
78
|
+
* The background style of the card, as a string identifier that matches a semanticColor token.
|
|
84
79
|
* This can be one of:
|
|
85
|
-
* - `"base-subtle"` `semanticColor.core.background.base.subtle`: a light gray background.
|
|
86
|
-
* - `"base-default"
|
|
80
|
+
* - `"base-subtle"` (color), `semanticColor.core.background.base.subtle`: a light gray background.
|
|
81
|
+
* - `"base-default"` (color), `semanticColor.core.background.base.default`: a white background.
|
|
87
82
|
*
|
|
88
83
|
* Default: `"base-default"`
|
|
89
84
|
*/
|
|
90
|
-
|
|
85
|
+
background?: "base-subtle" | "base-default";
|
|
91
86
|
/**
|
|
92
87
|
* The border radius of the card, as a string identifier that matches a border.radius token.
|
|
93
88
|
* This can be one of:
|
|
@@ -96,7 +91,7 @@ type StyleOnlyProps = {
|
|
|
96
91
|
*
|
|
97
92
|
* Default: `"radius_080"`
|
|
98
93
|
*/
|
|
99
|
-
|
|
94
|
+
borderRadius?: "small" | "medium";
|
|
100
95
|
/**
|
|
101
96
|
* The padding inside the card, as a string identifier that matches a sizing token.
|
|
102
97
|
* This can be one of:
|
|
@@ -117,8 +112,8 @@ type StyleOnlyProps = {
|
|
|
117
112
|
*/
|
|
118
113
|
elevation?: "none" | "low";
|
|
119
114
|
};
|
|
120
|
-
type Props = ConditionalProps;
|
|
121
115
|
|
|
116
|
+
type Props = BaseCardProps & TagProps & DismissProps;
|
|
122
117
|
/**
|
|
123
118
|
* The Card component is a flexible, reusable UI building block designed to
|
|
124
119
|
* encapsulate content within a structured, visually distinct container.
|
|
@@ -148,14 +143,14 @@ type Props = ConditionalProps;
|
|
|
148
143
|
*
|
|
149
144
|
* Cards can be customized via the following props:
|
|
150
145
|
*
|
|
151
|
-
* **`
|
|
146
|
+
* **`background` prop**
|
|
152
147
|
*
|
|
153
148
|
* | value | resolves to |
|
|
154
149
|
* |---|---|
|
|
155
150
|
* | `base-subtle` | `semanticColor.core.background.base.subtle` (light gray) |
|
|
156
151
|
* | `base-default` | `semanticColor.core.background.base.default` (white) |
|
|
157
152
|
*
|
|
158
|
-
* **`
|
|
153
|
+
* **`borderRadius` prop**
|
|
159
154
|
*
|
|
160
155
|
* | value | resolves to |
|
|
161
156
|
* |---|---|
|
|
@@ -194,8 +189,8 @@ const Card = React.forwardRef(function Card(
|
|
|
194
189
|
labels,
|
|
195
190
|
tag,
|
|
196
191
|
testId,
|
|
197
|
-
|
|
198
|
-
|
|
192
|
+
background = "base-default",
|
|
193
|
+
borderRadius = "small",
|
|
199
194
|
paddingSize = "small",
|
|
200
195
|
elevation = "none",
|
|
201
196
|
children,
|
|
@@ -204,8 +199,8 @@ const Card = React.forwardRef(function Card(
|
|
|
204
199
|
} = props;
|
|
205
200
|
|
|
206
201
|
const componentStyles = getComponentStyles({
|
|
207
|
-
|
|
208
|
-
|
|
202
|
+
background,
|
|
203
|
+
borderRadius,
|
|
209
204
|
paddingSize,
|
|
210
205
|
elevation,
|
|
211
206
|
});
|
|
@@ -230,11 +225,11 @@ const Card = React.forwardRef(function Card(
|
|
|
230
225
|
});
|
|
231
226
|
|
|
232
227
|
const getComponentStyles = ({
|
|
233
|
-
|
|
234
|
-
|
|
228
|
+
background,
|
|
229
|
+
borderRadius,
|
|
235
230
|
paddingSize,
|
|
236
231
|
elevation,
|
|
237
|
-
}:
|
|
232
|
+
}: StyleProps) => {
|
|
238
233
|
// Map prop values to tokens
|
|
239
234
|
const styleMap = {
|
|
240
235
|
backgroundColor: {
|
|
@@ -258,13 +253,10 @@ const getComponentStyles = ({
|
|
|
258
253
|
|
|
259
254
|
return StyleSheet.create({
|
|
260
255
|
root: {
|
|
261
|
-
backgroundColor:
|
|
262
|
-
backgroundColorStyle &&
|
|
263
|
-
styleMap.backgroundColor[backgroundColorStyle],
|
|
256
|
+
backgroundColor: background && styleMap.backgroundColor[background],
|
|
264
257
|
borderColor: semanticColor.core.border.neutral.subtle,
|
|
265
258
|
borderStyle: "solid",
|
|
266
|
-
borderRadius:
|
|
267
|
-
borderRadiusStyle && styleMap.borderRadius[borderRadiusStyle],
|
|
259
|
+
borderRadius: borderRadius && styleMap.borderRadius[borderRadius],
|
|
268
260
|
borderWidth: border.width.thin,
|
|
269
261
|
boxShadow: elevation && styleMap.elevation[elevation],
|
|
270
262
|
padding: paddingSize && styleMap.padding[paddingSize],
|