@khanacademy/wonder-blocks-banner 0.1.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-banner",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "design": "v1",
5
5
  "description": "Banner components for Wonder Blocks.",
6
6
  "main": "dist/index.js",
@@ -16,11 +16,11 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@babel/runtime": "^7.18.6",
19
- "@khanacademy/wonder-blocks-button": "^3.0.3",
19
+ "@khanacademy/wonder-blocks-button": "^3.0.5",
20
20
  "@khanacademy/wonder-blocks-color": "^1.2.0",
21
21
  "@khanacademy/wonder-blocks-core": "^4.4.0",
22
- "@khanacademy/wonder-blocks-icon": "^1.2.30",
23
- "@khanacademy/wonder-blocks-icon-button": "^3.4.11",
22
+ "@khanacademy/wonder-blocks-icon": "^1.2.31",
23
+ "@khanacademy/wonder-blocks-icon-button": "^3.4.13",
24
24
  "@khanacademy/wonder-blocks-spacing": "^3.0.5",
25
25
  "@khanacademy/wonder-blocks-typography": "^1.1.33"
26
26
  },
@@ -0,0 +1,89 @@
1
+ // @flow
2
+ const actionsMappings = {
3
+ none: null,
4
+ buttons: [
5
+ {title: "Button 1", onClick: () => {}},
6
+ {title: "Button 2", onClick: () => {}},
7
+ ],
8
+ links: [
9
+ {title: "Link 1", href: "/"},
10
+ {title: "Link 2", href: "/"},
11
+ ],
12
+ buttonAndLink: [
13
+ {title: "Button", onClick: () => {}},
14
+ {title: "Link", href: "/"},
15
+ ],
16
+ linkAndButton: [
17
+ {title: "Link", href: "/"},
18
+ {title: "Button", onClick: () => {}},
19
+ ],
20
+ };
21
+
22
+ const dismissMappings = {
23
+ none: null,
24
+ "log on click": () => {
25
+ // eslint-disable-next-line no-console
26
+ console.log("Dimiss button clicked!");
27
+ },
28
+ };
29
+
30
+ export default {
31
+ kind: {
32
+ control: {type: "select"},
33
+ defaultValue: "info",
34
+ description: "Determines the color and icon of the banner.",
35
+ options: ["info", "success", "warning", "critical"],
36
+ table: {
37
+ type: {summary: `"info" | "success" | "warning" | "critical"`},
38
+ defaultValue: {summary: `"info"`},
39
+ },
40
+ type: {required: false},
41
+ },
42
+ layout: {
43
+ control: {type: "select"},
44
+ description: "Determines the edge style of the Banner.",
45
+ options: ["floating", "full-width"],
46
+ table: {
47
+ type: {summary: `"floating" | "full-width"`},
48
+ },
49
+ type: {required: true},
50
+ },
51
+ text: {
52
+ control: {type: "text"},
53
+ description:
54
+ "Text on the banner (LabelSmall) or a node if you want something different.",
55
+ table: {type: {summary: "string | React.Node"}},
56
+ type: {required: true},
57
+ },
58
+ actions: {
59
+ control: {type: "select"},
60
+ description: `Links or tertiary Buttons that appear to the right of the
61
+ text.\n\nThe ActionTrigger must have either an onClick or an href
62
+ field, or both.`,
63
+ options: actionsMappings,
64
+ table: {
65
+ type: {
66
+ summary: "Array<ActionTrigger>",
67
+ detail: "type ActionTrigger = {|\n\ttitle: string,\n\tonClick: () => void | href: string,\n\tariaLabel?: string,\n|}",
68
+ },
69
+ },
70
+ },
71
+ onDismiss: {
72
+ control: {type: "select"},
73
+ description:
74
+ "If present, dismiss button is on right side. If not, no button appears.",
75
+ options: dismissMappings,
76
+ table: {required: false},
77
+ },
78
+ dismissAriaLabel: {
79
+ control: {type: "text"},
80
+ description:
81
+ "The accessible label for the dismiss button. Please pass in a translated string.",
82
+ defaultValue: "Dismiss banner.",
83
+ table: {
84
+ type: {summary: "string"},
85
+ defaultValue: {summary: `"Dismiss banner."`},
86
+ },
87
+ type: {required: false},
88
+ },
89
+ };
@@ -0,0 +1,468 @@
1
+ // @flow
2
+ import * as React from "react";
3
+ import {StyleSheet} from "aphrodite";
4
+
5
+ import Banner from "@khanacademy/wonder-blocks-banner";
6
+ import Button from "@khanacademy/wonder-blocks-button";
7
+ import Color from "@khanacademy/wonder-blocks-color";
8
+ import {View} from "@khanacademy/wonder-blocks-core";
9
+ import {Strut} from "@khanacademy/wonder-blocks-layout";
10
+ import Link from "@khanacademy/wonder-blocks-link";
11
+ import Spacing from "@khanacademy/wonder-blocks-spacing";
12
+ import {LabelSmall} from "@khanacademy/wonder-blocks-typography";
13
+
14
+ import type {StoryComponentType} from "@storybook/react";
15
+
16
+ import BannerArgTypes from "./banner.argtypes.js";
17
+ import ComponentInfo from "../../../../../.storybook/components/component-info.js";
18
+ import {name, version} from "../../../package.json";
19
+
20
+ const bannerDescription = `
21
+ Banner. A banner displays a prominent message and
22
+ related optional actions. It can be used as a way of
23
+ informing the user of important changes. Typically, it is
24
+ displayed toward the top of the screen.\n\nThere are two
25
+ possible layouts for banners - floating and full-width.
26
+ The \`floating\` layout is intended to be used when
27
+ there is whitespace around the banner. The \`full-width\`
28
+ layout is intended to be used when the banner needs to be
29
+ flush with surrounding elements.\n\n\n### Usage
30
+
31
+ \`\`\`jsx
32
+ import Banner from "@khanacademy/wonder-blocks-banner";
33
+
34
+ <Banner
35
+ text="Here is some example text."
36
+ kind="success"
37
+ layout="floating"
38
+ actions={[
39
+ {title: "Button 1", onClick: () => {}},
40
+ {title: "Button 2", onClick: () => {}},
41
+ ]}
42
+ onDismiss={() => {console.log("Has been dismissed.")}}
43
+ />
44
+ \`\`\`
45
+ `;
46
+
47
+ export default {
48
+ title: "Banner",
49
+ component: Banner,
50
+ decorators: [
51
+ (Story: StoryComponentType): React.Element<typeof View> => (
52
+ <View style={styles.example}>
53
+ <Story />
54
+ </View>
55
+ ),
56
+ ],
57
+ parameters: {
58
+ componentSubtitle: ((
59
+ <ComponentInfo name={name} version={version} />
60
+ ): any),
61
+ docs: {
62
+ description: {
63
+ component: bannerDescription,
64
+ },
65
+ source: {
66
+ // See https://github.com/storybookjs/storybook/issues/12596
67
+ excludeDecorators: true,
68
+ },
69
+ },
70
+ },
71
+ argTypes: BannerArgTypes,
72
+ };
73
+
74
+ export const Default: StoryComponentType = (args) => <Banner {...args} />;
75
+
76
+ Default.args = {
77
+ text: "Here is some example text.",
78
+ };
79
+
80
+ export const Simple: StoryComponentType = () => (
81
+ <View style={styles.container}>
82
+ <Banner text="This is some example text." layout="floating" />
83
+ <Strut size={Spacing.medium_16} />
84
+ <Banner text="This is some example text." layout="full-width" />
85
+ </View>
86
+ );
87
+
88
+ Simple.parameters = {
89
+ docs: {
90
+ storyDescription: `These are examples of banners with just
91
+ the \`text\` prop and the \`layout\` prop. `,
92
+ },
93
+ };
94
+
95
+ export const Kinds: StoryComponentType = () => (
96
+ <View style={styles.container}>
97
+ <Banner
98
+ text="kind: info - This is a message about something informative like an announcement."
99
+ kind="info"
100
+ layout="floating"
101
+ />
102
+ <Strut size={Spacing.medium_16} />
103
+ <Banner
104
+ text="kind: success - This is a message about something positive or successful!"
105
+ kind="success"
106
+ layout="floating"
107
+ />
108
+ <Strut size={Spacing.medium_16} />
109
+ <Banner
110
+ text="kind: warning - This is a message warning the user about a potential issue."
111
+ kind="warning"
112
+ layout="floating"
113
+ />
114
+ <Strut size={Spacing.medium_16} />
115
+ <Banner
116
+ text="kind: critical - This is a message about something critical or an error."
117
+ kind="critical"
118
+ layout="floating"
119
+ />
120
+ </View>
121
+ );
122
+
123
+ Kinds.parameters = {
124
+ docs: {
125
+ storyDescription: `Banners have four possible kinds (\`kind\` prop) -
126
+ info (default), success, warning, and critical. Info is blue
127
+ with an info "i" icon, success is green with a smiling icon,
128
+ warning is yellow with a triangular "!" icon, and critical is
129
+ red with a round "!" icon.`,
130
+ },
131
+ };
132
+
133
+ export const Layouts: StoryComponentType = () => {
134
+ const borderStyle = {border: `2px solid ${Color.pink}`};
135
+ const floatingContainerStyle = {padding: Spacing.xSmall_8};
136
+
137
+ return (
138
+ <View style={styles.container}>
139
+ <Banner
140
+ text="This banner has full-width layout."
141
+ layout="full-width"
142
+ kind="success"
143
+ />
144
+ <Strut size={Spacing.medium_16} />
145
+ <Banner
146
+ text="This banner has floating layout."
147
+ layout="floating"
148
+ kind="success"
149
+ />
150
+ <Strut size={Spacing.medium_16} />
151
+ <View style={borderStyle}>
152
+ <Banner
153
+ text="This banner has full-width layout. There is no space around it."
154
+ layout="full-width"
155
+ kind="success"
156
+ />
157
+ </View>
158
+ <Strut size={Spacing.medium_16} />
159
+ <View style={[borderStyle, floatingContainerStyle]}>
160
+ <Banner
161
+ text={`This banner has floating layout. Padding has been
162
+ added to its container manually in order for the
163
+ banner to not touch any other elements.`}
164
+ layout="floating"
165
+ kind="success"
166
+ />
167
+ </View>
168
+ </View>
169
+ );
170
+ };
171
+
172
+ Layouts.parameters = {
173
+ backgrounds: {
174
+ default: "darkBlue",
175
+ },
176
+ docs: {
177
+ storyDescription: `Banners come with two layouts: full-width
178
+ and floating. Full-width layout gives the banner squared edges,
179
+ and floating layout gives the banner rounded edges. Floating
180
+ banners should have space around them and should not be touching
181
+ other components. The space around floating banners is not
182
+ automatically added to the container, it must be manually managed
183
+ by the developer. To demonstrate this, there are also examples with
184
+ outlines around them - the full-width banner is touching its outline,
185
+ but padding has been added around the floating banner
186
+ so that it will not touch its outline.`,
187
+ },
188
+ };
189
+
190
+ export const DarkBackground: StoryComponentType = () => (
191
+ <View style={styles.container}>
192
+ <Banner text="kind: info" kind="info" layout="full-width" />
193
+ <Strut size={Spacing.medium_16} />
194
+ <Banner text="kind: success" kind="success" layout="full-width" />
195
+ <Strut size={Spacing.medium_16} />
196
+ <Banner text="kind: warning" kind="warning" layout="full-width" />
197
+ <Strut size={Spacing.medium_16} />
198
+ <Banner text="kind: critical" kind="critical" layout="full-width" />
199
+ </View>
200
+ );
201
+
202
+ DarkBackground.parameters = {
203
+ backgrounds: {
204
+ default: "darkBlue",
205
+ },
206
+ docs: {
207
+ storyDescription: "This is how banners look on a dark background.",
208
+ },
209
+ };
210
+
211
+ export const WithButtons: StoryComponentType = () => (
212
+ <Banner
213
+ text="This is a banner with buttons."
214
+ layout="full-width"
215
+ actions={[
216
+ {title: "Button 1", onClick: () => {}},
217
+ {title: "Button 2", onClick: () => {}},
218
+ ]}
219
+ />
220
+ );
221
+
222
+ WithButtons.parameters = {
223
+ docs: {
224
+ storyDescription: `This is a banner with buttons. An action, passed
225
+ into the \`actions\` prop, becomes a button when it has an
226
+ \`onClick\` value and does not have an \`href\` value.`,
227
+ },
228
+ };
229
+
230
+ export const WithLinks: StoryComponentType = () => (
231
+ <Banner
232
+ text="This is a banner with links."
233
+ layout="floating"
234
+ actions={[
235
+ {title: "Link 1", href: "/"},
236
+ {title: "Link 2", href: "/", onClick: () => {}},
237
+ ]}
238
+ />
239
+ );
240
+
241
+ WithLinks.parameters = {
242
+ docs: {
243
+ storyDescription: `This is a banner with links. An action, passed
244
+ into the \`actions\` prop, becomes a link when it has an
245
+ \`href\` value. It can also have an \`onClick\` value, but it
246
+ will be a link regardless if it navigates to a URL via \`href\`.`,
247
+ },
248
+ };
249
+
250
+ export const WithInlineLinks: StoryComponentType = () => (
251
+ <>
252
+ <Banner
253
+ text="Oh no! The button and link on the right look different! Don't mix button and link actions."
254
+ kind="critical"
255
+ layout="floating"
256
+ actions={[
257
+ {title: "Link", href: "/"},
258
+ {title: "Button", onClick: () => {}},
259
+ ]}
260
+ />
261
+ <Strut size={Spacing.medium_16} />
262
+ <Banner
263
+ text={
264
+ <LabelSmall>
265
+ Use inline links in the body of the text instead. Click{" "}
266
+ {<Link href="">here</Link>} to go to some other page.
267
+ </LabelSmall>
268
+ }
269
+ kind="success"
270
+ layout="floating"
271
+ actions={[{title: "Button", onClick: () => {}}]}
272
+ />
273
+ </>
274
+ );
275
+
276
+ WithInlineLinks.parameters = {
277
+ docs: {
278
+ storyDescription: `A banner can have inline links passed into
279
+ the \`text\` prop. Here, the Wonder Blocks \`<Link>\` component
280
+ is inline with the text that is in a span. \n\nOne place to use
281
+ this is in the case that a banner needs to have a link action
282
+ _and_ a button action. That is to say, one action navigates
283
+ to a URL and the other doesn't. This may be unfavorable because
284
+ buttons and links look different. One workaround is to make the
285
+ link inline and only have buttons as actions.`,
286
+ },
287
+ };
288
+
289
+ export const Multiline: StoryComponentType = () => (
290
+ <View style={styles.narrowBanner}>
291
+ <Banner
292
+ text={
293
+ "This is a multi-line banner. These have wrapping text and actions would be below."
294
+ }
295
+ layout="full-width"
296
+ />
297
+ </View>
298
+ );
299
+
300
+ Multiline.parameters = {
301
+ docs: {
302
+ storyDescription:
303
+ "This is an example of a banner with multiple lines of text.",
304
+ },
305
+ };
306
+
307
+ export const MultilineWithButtons: StoryComponentType = () => (
308
+ <View style={styles.narrowBanner}>
309
+ <Banner
310
+ text={
311
+ "This is a multi-line banner. These have wrapping text and actions are below."
312
+ }
313
+ actions={[
314
+ {title: "Button 1", onClick: () => {}},
315
+ {title: "Button 2", onClick: () => {}},
316
+ ]}
317
+ layout="floating"
318
+ />
319
+ </View>
320
+ );
321
+
322
+ MultilineWithButtons.parameters = {
323
+ docs: {
324
+ storyDescription: `When a banner has long text, the actions
325
+ move from the right of the text to the bottom. Here, the
326
+ actions are buttons.`,
327
+ },
328
+ };
329
+
330
+ export const MultilineWithLinks: StoryComponentType = () => (
331
+ <View style={styles.narrowBanner}>
332
+ <Banner
333
+ text={
334
+ "This is a multi-line banner. These have wrapping text and actions are below."
335
+ }
336
+ actions={[
337
+ {title: "Link 1", href: "/"},
338
+ {title: "Link 2", href: "/"},
339
+ ]}
340
+ layout="full-width"
341
+ />
342
+ </View>
343
+ );
344
+
345
+ MultilineWithLinks.parameters = {
346
+ docs: {
347
+ storyDescription: `When a banner has long text, the actions
348
+ move from the right of the text to the bottom. Here, the
349
+ actions are links.`,
350
+ },
351
+ };
352
+
353
+ export const WithDismissal: StoryComponentType = () => {
354
+ const [dismissed, setDismissed] = React.useState(false);
355
+
356
+ const handleDismiss = () => {
357
+ // eslint-disable-next-line no-console
358
+ console.log("Dismiss!");
359
+ setDismissed(true);
360
+ };
361
+
362
+ const handleReset = () => {
363
+ // eslint-disable-next-line no-console
364
+ console.log("Reset!");
365
+ setDismissed(false);
366
+ };
367
+
368
+ return dismissed ? (
369
+ <Button onClick={handleReset}>Reset banner</Button>
370
+ ) : (
371
+ <Banner
372
+ text="This banner can be dismissed"
373
+ kind="critical"
374
+ onDismiss={handleDismiss}
375
+ actions={[{title: "Also dismiss", onClick: handleDismiss}]}
376
+ layout="floating"
377
+ />
378
+ );
379
+ };
380
+
381
+ WithDismissal.parameters = {
382
+ docs: {
383
+ storyDescription: `This is a banner that can be dismissed. For the
384
+ "X" dismiss button to show up, a function must be passed into
385
+ the \`onDismiss\` prop.\n\nHere, pressing the "X" button or the
386
+ "Also dismiss" button will dismiss the banner. Pressing either
387
+ button sets the \`dismissed\` state to true, which causes the
388
+ banner not to render due to a conditional. Instead, there is a
389
+ button whose \`onClick\` function sets the \`dismissed\` state
390
+ to false. This causes the banner to reappear and the button to
391
+ disappear.`,
392
+ },
393
+ };
394
+
395
+ export const RightToLeft: StoryComponentType = () => (
396
+ <View style={styles.rightToLeft}>
397
+ <Banner
398
+ text="یہ اردو میں لکھا ہے۔"
399
+ actions={[
400
+ {title: "پہلا بٹن", onClick: () => {}},
401
+ {title: "دوسرا بٹن", onClick: () => {}},
402
+ ]}
403
+ layout="full-width"
404
+ />
405
+ <Strut size={Spacing.medium_16} />
406
+ <Banner
407
+ text="یہ اردو میں لکھا ہے۔"
408
+ actions={[
409
+ {title: "پہلا بٹن", onClick: () => {}},
410
+ {title: "دوسرا بٹن", onClick: () => {}},
411
+ ]}
412
+ layout="floating"
413
+ />
414
+ </View>
415
+ );
416
+
417
+ RightToLeft.parameters = {
418
+ docs: {
419
+ storyDescription: `When in the right-to-left direction, the banner
420
+ is mirrored. This example has text in Urdu, which is a
421
+ right-to-left language.`,
422
+ },
423
+ };
424
+
425
+ export const RightToLeftMultiline: StoryComponentType = () => (
426
+ <View style={styles.rightToLeft}>
427
+ <Banner
428
+ text={`یہ اردو میں لکھا ہے۔یہ اردو میں لکھا ہے۔یہ اردو میں لکھا ہے۔یہ
429
+ اردو میں لکھا ہے۔یہ اردو میں لکھا ہے۔یہ اردو میں لکھا ہے۔یہ
430
+ اردو میں لکھا ہے۔یہ اردو میں لکھا ہے۔یہ اردو میں لکھا ہے۔یہ
431
+ اردو میں لکھا ہے۔یہ اردو میں لکھا ہے۔یہ اردو میں لکھا ہے۔یہ
432
+ اردو میں لکھا ہے۔یہ اردو میں لکھا ہے۔یہ اردو میں لکھا ہے۔
433
+ اردو میں لکھا ہے۔یہ اردو میں لکھا ہے۔یہ اردو میں لکھا ہے۔
434
+ اردو میں لکھا ہے۔یہ اردو میں لکھا ہے۔یہ اردو میں لکھا ہے۔`}
435
+ actions={[
436
+ {title: "پہلا بٹن", onClick: () => {}},
437
+ {title: "دوسرا بٹن", onClick: () => {}},
438
+ ]}
439
+ layout="full-width"
440
+ />
441
+ </View>
442
+ );
443
+
444
+ RightToLeftMultiline.parameters = {
445
+ docs: {
446
+ storyDescription: `When in the right-to-left direction, the banner
447
+ is mirrored. This example has text in Urdu, which is a
448
+ right-to-left language. This example also has multiple lines
449
+ with the butotns on the bottom of the text.`,
450
+ },
451
+ };
452
+
453
+ const styles = StyleSheet.create({
454
+ example: {
455
+ alignItems: "center",
456
+ justifyContent: "center",
457
+ },
458
+ container: {
459
+ width: "100%",
460
+ },
461
+ narrowBanner: {
462
+ maxWidth: 400,
463
+ },
464
+ rightToLeft: {
465
+ width: "100%",
466
+ direction: "rtl",
467
+ },
468
+ });