@khanacademy/wonder-blocks-card 0.0.0-PR2799-20250929232634 → 0.0.0-PR2799-20251001174203
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 +5 -1
- 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 +43 -28
package/CHANGELOG.md
CHANGED
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:
|
|
@@ -107,9 +102,18 @@ type StyleOnlyProps = {
|
|
|
107
102
|
* Default: `"size_160"`
|
|
108
103
|
*/
|
|
109
104
|
paddingSize?: "none" | "small" | "medium";
|
|
105
|
+
/**
|
|
106
|
+
* The box-shadow for the card, as a string identifier that matches a sizing token.
|
|
107
|
+
* This can be one of:
|
|
108
|
+
* - `"none"`: no elevation.
|
|
109
|
+
* - `"low"`, matching `boxShadow.low`.
|
|
110
|
+
*
|
|
111
|
+
* Default: `"none"`
|
|
112
|
+
*/
|
|
113
|
+
elevation?: "none" | "low";
|
|
110
114
|
};
|
|
111
|
-
type Props = ConditionalProps;
|
|
112
115
|
|
|
116
|
+
type Props = BaseCardProps & TagProps & DismissProps;
|
|
113
117
|
/**
|
|
114
118
|
* The Card component is a flexible, reusable UI building block designed to
|
|
115
119
|
* encapsulate content within a structured, visually distinct container.
|
|
@@ -139,20 +143,27 @@ type Props = ConditionalProps;
|
|
|
139
143
|
*
|
|
140
144
|
* Cards can be customized via the following props:
|
|
141
145
|
*
|
|
142
|
-
* **`
|
|
146
|
+
* **`background` prop**
|
|
143
147
|
*
|
|
144
148
|
* | value | resolves to |
|
|
145
149
|
* |---|---|
|
|
146
150
|
* | `base-subtle` | `semanticColor.core.background.base.subtle` (light gray) |
|
|
147
151
|
* | `base-default` | `semanticColor.core.background.base.default` (white) |
|
|
148
152
|
*
|
|
149
|
-
* **`
|
|
153
|
+
* **`borderRadius` prop**
|
|
150
154
|
*
|
|
151
155
|
* | value | resolves to |
|
|
152
156
|
* |---|---|
|
|
153
157
|
* | `small` | `border.radius.radius_080` |
|
|
154
158
|
* | `medium` | `border.radius.radius_120` |
|
|
155
159
|
*
|
|
160
|
+
* **`elevation` prop**
|
|
161
|
+
*
|
|
162
|
+
* | value | resolves to |
|
|
163
|
+
* |---|---|
|
|
164
|
+
* | `none` | `none` |
|
|
165
|
+
* | `low` | `boxShadow.low` |
|
|
166
|
+
*
|
|
156
167
|
* **`paddingSize` prop**
|
|
157
168
|
*
|
|
158
169
|
* | value | resolves to |
|
|
@@ -178,18 +189,20 @@ const Card = React.forwardRef(function Card(
|
|
|
178
189
|
labels,
|
|
179
190
|
tag,
|
|
180
191
|
testId,
|
|
181
|
-
|
|
182
|
-
|
|
192
|
+
background = "base-default",
|
|
193
|
+
borderRadius = "small",
|
|
183
194
|
paddingSize = "small",
|
|
195
|
+
elevation = "none",
|
|
184
196
|
children,
|
|
185
197
|
onDismiss,
|
|
186
198
|
inert,
|
|
187
199
|
} = props;
|
|
188
200
|
|
|
189
201
|
const componentStyles = getComponentStyles({
|
|
190
|
-
|
|
191
|
-
|
|
202
|
+
background,
|
|
203
|
+
borderRadius,
|
|
192
204
|
paddingSize,
|
|
205
|
+
elevation,
|
|
193
206
|
});
|
|
194
207
|
return (
|
|
195
208
|
<View
|
|
@@ -212,10 +225,11 @@ const Card = React.forwardRef(function Card(
|
|
|
212
225
|
});
|
|
213
226
|
|
|
214
227
|
const getComponentStyles = ({
|
|
215
|
-
|
|
216
|
-
|
|
228
|
+
background,
|
|
229
|
+
borderRadius,
|
|
217
230
|
paddingSize,
|
|
218
|
-
|
|
231
|
+
elevation,
|
|
232
|
+
}: StyleProps) => {
|
|
219
233
|
// Map prop values to tokens
|
|
220
234
|
const styleMap = {
|
|
221
235
|
backgroundColor: {
|
|
@@ -231,19 +245,20 @@ const getComponentStyles = ({
|
|
|
231
245
|
small: sizing.size_160,
|
|
232
246
|
medium: sizing.size_240,
|
|
233
247
|
},
|
|
248
|
+
elevation: {
|
|
249
|
+
none: "none",
|
|
250
|
+
low: boxShadow.low,
|
|
251
|
+
},
|
|
234
252
|
} as const;
|
|
235
253
|
|
|
236
254
|
return StyleSheet.create({
|
|
237
255
|
root: {
|
|
238
|
-
backgroundColor:
|
|
239
|
-
backgroundColorStyle &&
|
|
240
|
-
styleMap.backgroundColor[backgroundColorStyle],
|
|
256
|
+
backgroundColor: background && styleMap.backgroundColor[background],
|
|
241
257
|
borderColor: semanticColor.core.border.neutral.subtle,
|
|
242
258
|
borderStyle: "solid",
|
|
243
|
-
borderRadius:
|
|
244
|
-
borderRadiusStyle && styleMap.borderRadius[borderRadiusStyle],
|
|
259
|
+
borderRadius: borderRadius && styleMap.borderRadius[borderRadius],
|
|
245
260
|
borderWidth: border.width.thin,
|
|
246
|
-
boxShadow:
|
|
261
|
+
boxShadow: elevation && styleMap.elevation[elevation],
|
|
247
262
|
padding: paddingSize && styleMap.padding[paddingSize],
|
|
248
263
|
minInlineSize: sizing.size_280,
|
|
249
264
|
position: "relative",
|