@khanacademy/wonder-blocks-cell 1.0.0

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 ADDED
@@ -0,0 +1,17 @@
1
+ # @khanacademy/wonder-blocks-cell
2
+
3
+ ## 1.0.0
4
+ ### Major Changes
5
+
6
+ - dc6c5f05: Add DetailCell component
7
+
8
+ ### Minor Changes
9
+
10
+ - dc6c5f05: Add horizontalRule, style and testId to BasicCell
11
+ - dc6c5f05: Add BasicCell and also title, leftAccessory, rightAccessory props
12
+ - dc6c5f05: Add states (active, disabled, hovered, focused, pressed), onClick and aria-label
13
+ - dc6c5f05: Adds `wonder-blocks-cell` package
14
+
15
+ ### Patch Changes
16
+
17
+ - 2855fa1f: Add WB layout as a dependency
@@ -0,0 +1,351 @@
1
+ import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/objectWithoutPropertiesLoose';
2
+ import * as React from 'react';
3
+ import { LabelMedium, LabelLarge, LabelSmall } from '@khanacademy/wonder-blocks-typography';
4
+ import _extends from '@babel/runtime/helpers/extends';
5
+ import { StyleSheet } from 'aphrodite';
6
+ import Clickable from '@khanacademy/wonder-blocks-clickable';
7
+ import { View } from '@khanacademy/wonder-blocks-core';
8
+ import Color, { fade } from '@khanacademy/wonder-blocks-color';
9
+ import { Strut } from '@khanacademy/wonder-blocks-layout';
10
+ import Spacing from '@khanacademy/wonder-blocks-spacing';
11
+
12
+ const CellMeasurements = {
13
+ cellMinHeight: Spacing.xxLarge_48,
14
+
15
+ /**
16
+ * The cell wrapper's gap.
17
+ */
18
+ cellPadding: {
19
+ paddingVertical: Spacing.xSmall_8,
20
+ paddingHorizontal: Spacing.medium_16
21
+ },
22
+
23
+ /**
24
+ * The extra vertical spacing added to the title/content wrapper.
25
+ */
26
+ contentVerticalSpacing: Spacing.xxxSmall_4,
27
+
28
+ /**
29
+ * The horizontal spacing between the left and right accessory.
30
+ */
31
+ accessoryHorizontalSpacing: Spacing.medium_16
32
+ };
33
+ /**
34
+ * Gets the horizontalRule style based on the variant.
35
+ * @param {HorizontalRuleVariant} horizontalRule The variant of the horizontal
36
+ * rule.
37
+ * @returns A styled horizontal rule.
38
+ */
39
+
40
+ const getHorizontalRuleStyles = horizontalRule => {
41
+ switch (horizontalRule) {
42
+ case "inset":
43
+ return [styles$2.horizontalRule, styles$2.horizontalRuleInset];
44
+
45
+ case "full-width":
46
+ return styles$2.horizontalRule;
47
+
48
+ case "none":
49
+ return {};
50
+ }
51
+ };
52
+ const styles$2 = StyleSheet.create({
53
+ horizontalRule: {
54
+ position: "relative",
55
+ ":after": {
56
+ width: "100%",
57
+ content: "''",
58
+ position: "absolute",
59
+ // align to the bottom of the cell
60
+ bottom: 0,
61
+ // align border to the right of the cell
62
+ right: 0,
63
+ height: Spacing.xxxxSmall_2,
64
+ boxShadow: `inset 0px -1px 0px ${Color.offBlack8}`
65
+ }
66
+ },
67
+ horizontalRuleInset: {
68
+ ":after": {
69
+ // Inset doesn't include the left padding of the cell.
70
+ width: `calc(100% - ${CellMeasurements.cellPadding.paddingHorizontal}px)`
71
+ }
72
+ }
73
+ });
74
+
75
+ /**
76
+ * Left Accessories can be defined using WB components such as Icon, IconButton,
77
+ * or it can even be used for a custom node/component if needed.
78
+ */
79
+ const LeftAccessory = ({
80
+ leftAccessory,
81
+ leftAccessoryStyle,
82
+ disabled
83
+ }) => {
84
+ if (!leftAccessory) {
85
+ return null;
86
+ }
87
+
88
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(View, {
89
+ style: [styles$1.accessory, disabled && styles$1.accessoryDisabled, _extends({}, leftAccessoryStyle)]
90
+ }, leftAccessory), /*#__PURE__*/React.createElement(Strut, {
91
+ size: CellMeasurements.accessoryHorizontalSpacing
92
+ }));
93
+ };
94
+
95
+ /**
96
+ * Right Accessories can be defined using WB components such as Icon,
97
+ * IconButton, or it can even be used for a custom node/component if needed.
98
+ */
99
+ const RightAccessory = ({
100
+ rightAccessory,
101
+ rightAccessoryStyle,
102
+ active,
103
+ disabled
104
+ }) => {
105
+ if (!rightAccessory) {
106
+ return null;
107
+ }
108
+
109
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Strut, {
110
+ size: CellMeasurements.accessoryHorizontalSpacing
111
+ }), /*#__PURE__*/React.createElement(View, {
112
+ style: [styles$1.accessory, styles$1.accessoryRight, disabled && styles$1.accessoryDisabled, _extends({}, rightAccessoryStyle), active && styles$1.accessoryActive]
113
+ }, rightAccessory));
114
+ };
115
+
116
+ /**
117
+ * CellCore is the base cell wrapper. It's used as the skeleton/layout that is
118
+ * used by BasicCell and DetailCell (and any other variants).
119
+ *
120
+ * Both variants share how they render their accessories, and the main
121
+ * responsibility of this component is to render the contents that are passed in
122
+ * (using the `children` prop).
123
+ */
124
+ const CellCore = props => {
125
+ const {
126
+ active,
127
+ children,
128
+ disabled,
129
+ horizontalRule = "inset",
130
+ leftAccessory = undefined,
131
+ leftAccessoryStyle = undefined,
132
+ onClick,
133
+ rightAccessory = undefined,
134
+ rightAccessoryStyle = undefined,
135
+ style,
136
+ testId,
137
+ "aria-label": ariaLabel
138
+ } = props;
139
+
140
+ const renderCell = eventState => {
141
+ const horizontalRuleStyles = getHorizontalRuleStyles(horizontalRule);
142
+ return /*#__PURE__*/React.createElement(View, {
143
+ style: [styles$1.wrapper, // focused applied to the main wrapper to make the border
144
+ // outline part of the wrapper
145
+ (eventState == null ? void 0 : eventState.focused) && styles$1.focused, // custom styles
146
+ style],
147
+ "aria-current": active ? "true" : undefined
148
+ }, /*#__PURE__*/React.createElement(View, {
149
+ style: [styles$1.innerWrapper, horizontalRuleStyles, disabled && styles$1.disabled, active && styles$1.active, // other states applied to the inner wrapper to blend
150
+ // the background color properly
151
+ !disabled && (eventState == null ? void 0 : eventState.hovered) && styles$1.hovered, // active + hovered
152
+ active && (eventState == null ? void 0 : eventState.hovered) && styles$1.activeHovered, !disabled && (eventState == null ? void 0 : eventState.pressed) && styles$1.pressed, // active + pressed
153
+ !disabled && active && (eventState == null ? void 0 : eventState.pressed) && styles$1.activePressed]
154
+ }, /*#__PURE__*/React.createElement(LeftAccessory, {
155
+ leftAccessory: leftAccessory,
156
+ leftAccessoryStyle: leftAccessoryStyle,
157
+ disabled: disabled
158
+ }), /*#__PURE__*/React.createElement(View, {
159
+ style: styles$1.content,
160
+ testId: testId
161
+ }, children), /*#__PURE__*/React.createElement(RightAccessory, {
162
+ rightAccessory: rightAccessory,
163
+ rightAccessoryStyle: rightAccessoryStyle,
164
+ active: active,
165
+ disabled: disabled
166
+ })));
167
+ }; // Pressable cell.
168
+
169
+
170
+ if (onClick) {
171
+ return /*#__PURE__*/React.createElement(Clickable, {
172
+ disabled: disabled,
173
+ onClick: onClick,
174
+ hideDefaultFocusRing: true,
175
+ "aria-label": ariaLabel ? ariaLabel : undefined
176
+ }, eventState => renderCell(eventState));
177
+ } // No click event attached, so just render the cell as-is.
178
+
179
+
180
+ return renderCell();
181
+ };
182
+
183
+ const styles$1 = StyleSheet.create({
184
+ wrapper: {
185
+ background: Color.white,
186
+ color: Color.offBlack,
187
+ minHeight: CellMeasurements.cellMinHeight,
188
+ textAlign: "left"
189
+ },
190
+ innerWrapper: {
191
+ padding: `${CellMeasurements.cellPadding.paddingVertical}px ${CellMeasurements.cellPadding.paddingHorizontal}px`,
192
+ flexDirection: "row",
193
+ flex: 1
194
+ },
195
+ content: {
196
+ alignSelf: "center",
197
+ overflowWrap: "break-word",
198
+ padding: `${CellMeasurements.contentVerticalSpacing}px 0`
199
+ },
200
+ accessory: {
201
+ // Use content width by default.
202
+ minWidth: "auto",
203
+ // Horizontal alignment of the accessory.
204
+ alignItems: "center",
205
+ // Vertical alignment.
206
+ alignSelf: "center"
207
+ },
208
+ accessoryRight: {
209
+ // The right accessory will have this color by default. Unless the
210
+ // accessory element overrides that color internally.
211
+ color: Color.offBlack64,
212
+ // Align the right accessory to the right side of the cell, so we can
213
+ // prevent the accessory from shifting left, if the content is too
214
+ // short.
215
+ marginLeft: "auto"
216
+ },
217
+
218
+ /**
219
+ * States
220
+ */
221
+ hovered: {
222
+ background: Color.offBlack8
223
+ },
224
+ // Handling the focus ring internally because clickable doesn't support
225
+ // rounded focus ring.
226
+ focused: {
227
+ borderRadius: Spacing.xxxSmall_4,
228
+ outline: `solid ${Spacing.xxxxSmall_2}px ${Color.blue}`,
229
+ // The focus ring is not visible when there are stacked cells.
230
+ // Using outlineOffset to display the focus ring inside the cell.
231
+ outlineOffset: -Spacing.xxxxSmall_2,
232
+ // To hide the internal corners of the cell.
233
+ overflow: "hidden"
234
+ },
235
+ pressed: {
236
+ background: Color.offBlack16
237
+ },
238
+ active: {
239
+ background: fade(Color.blue, 0.08),
240
+ color: Color.blue
241
+ },
242
+ activeHovered: {
243
+ background: fade(Color.blue, 0.16)
244
+ },
245
+ activePressed: {
246
+ background: fade(Color.blue, 0.24)
247
+ },
248
+ disabled: {
249
+ color: Color.offBlack32
250
+ },
251
+ accessoryActive: {
252
+ color: Color.blue
253
+ },
254
+ accessoryDisabled: {
255
+ color: Color.offBlack,
256
+ opacity: 0.32
257
+ }
258
+ });
259
+
260
+ const _excluded$1 = ["title"];
261
+
262
+ /**
263
+ * BasicCell is the simplest form of the Cell. It is a compacted-height Cell
264
+ * with limited subviews and accessories, to be used for simple lists, like
265
+ * dropdown option items, navigation items, settings dialogs, etc.
266
+ *
267
+ * ### Usage
268
+ *
269
+ * ```jsx
270
+ * import {BasicCell} from "@khanacademy/wonder-blocks-cell";
271
+ *
272
+ * <BasicCell
273
+ * title="Basic cell"
274
+ * rightAccessory={<Icon icon={icons.caretRight} size="medium" />}
275
+ * />
276
+ * ```
277
+ */
278
+ function BasicCell(props) {
279
+ const {
280
+ title
281
+ } = props,
282
+ coreProps = _objectWithoutPropertiesLoose(props, _excluded$1);
283
+
284
+ return /*#__PURE__*/React.createElement(CellCore, coreProps, typeof title === "string" ? /*#__PURE__*/React.createElement(LabelMedium, null, title) : title);
285
+ }
286
+
287
+ const _excluded = ["title", "subtitle1", "subtitle2"];
288
+
289
+ const Subtitle = ({
290
+ subtitle,
291
+ disabled
292
+ }) => {
293
+ if (!subtitle) {
294
+ return null;
295
+ }
296
+
297
+ if (typeof subtitle === "string") {
298
+ return /*#__PURE__*/React.createElement(LabelSmall, {
299
+ style: !disabled && styles.subtitle
300
+ }, subtitle);
301
+ }
302
+
303
+ return subtitle;
304
+ };
305
+
306
+ /**
307
+ * This is a variant of BasicCell that allows adding subtitles, before and after
308
+ * the cell title.
309
+ *
310
+ * ### Usage
311
+ *
312
+ * ```jsx
313
+ * import {DetailCell} from "@khanacademy/wonder-blocks-cell";
314
+ *
315
+ * <DetailCell
316
+ * leftAccessory={<Icon icon={icons.contentVideo} size="medium" />}
317
+ * subtitle1="Subtitle 1"
318
+ * title="Detail cell"
319
+ * subtitle1="Subtitle 2"
320
+ * rightAccessory={<Icon icon={icons.caretRight} size="medium" />}
321
+ * />
322
+ * ```
323
+ */
324
+ function DetailCell(props) {
325
+ const {
326
+ title,
327
+ subtitle1,
328
+ subtitle2
329
+ } = props,
330
+ coreProps = _objectWithoutPropertiesLoose(props, _excluded);
331
+
332
+ return /*#__PURE__*/React.createElement(CellCore, coreProps, /*#__PURE__*/React.createElement(Subtitle, {
333
+ subtitle: subtitle1,
334
+ disabled: coreProps.disabled
335
+ }), subtitle1 && /*#__PURE__*/React.createElement(Strut, {
336
+ size: Spacing.xxxxSmall_2
337
+ }), typeof title === "string" ? /*#__PURE__*/React.createElement(LabelLarge, null, title) : title, subtitle2 && /*#__PURE__*/React.createElement(Strut, {
338
+ size: Spacing.xxxxSmall_2
339
+ }), /*#__PURE__*/React.createElement(Subtitle, {
340
+ subtitle: subtitle2,
341
+ disabled: coreProps.disabled
342
+ }));
343
+ }
344
+
345
+ const styles = StyleSheet.create({
346
+ subtitle: {
347
+ color: Color.offBlack64
348
+ }
349
+ });
350
+
351
+ export { BasicCell, DetailCell };