@khanacademy/wonder-blocks-dropdown 3.1.10 → 4.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 CHANGED
@@ -1,5 +1,22 @@
1
1
  # @khanacademy/wonder-blocks-dropdown
2
2
 
3
+ ## 4.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 860d9ef9: Allow custom `ActionItem` components by using Cell internally.
8
+
9
+ - Removed `ClickableBehavior` from `ActionItem` and replaced it with
10
+ `CompactCell` (which internally uses `Clickable`).
11
+ - Removed `skipClientNav` from `ActionItem` as it is no longer used/needed.
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies [860d9ef9]
16
+ - @khanacademy/wonder-blocks-cell@3.1.0
17
+ - @khanacademy/wonder-blocks-modal@4.0.38
18
+ - @khanacademy/wonder-blocks-search-field@2.1.25
19
+
3
20
  ## 3.1.10
4
21
 
5
22
  ### Patch Changes
@@ -1,10 +1,12 @@
1
1
  import * as React from "react";
2
- import type { StyleType } from "@khanacademy/wonder-blocks-core";
2
+ import { CompactCell } from "@khanacademy/wonder-blocks-cell";
3
+ import type { PropsFor, StyleType } from "@khanacademy/wonder-blocks-core";
4
+ type CompactCellProps = PropsFor<typeof CompactCell>;
3
5
  type ActionProps = {
4
6
  /**
5
7
  * Display text of the action item.
6
8
  */
7
- label: string;
9
+ label: string | CompactCellProps["title"];
8
10
  /**
9
11
  * Whether this action item is disabled.
10
12
  */
@@ -23,22 +25,9 @@ type ActionProps = {
23
25
  /**
24
26
  * A target destination window for a link to open in.
25
27
  *
26
- * TODO(WB-1262): only allow this prop when `href` is also set.t
28
+ * TODO(WB-1262): only allow this prop when `href` is also set.
27
29
  */
28
30
  target?: "_blank";
29
- /**
30
- * Whether to avoid using client-side navigation.
31
- *
32
- * If the URL passed to href is local to the client-side, e.g.
33
- * /math/algebra/eval-exprs, then it tries to use react-router-dom's Link
34
- * component which handles the client-side navigation. You can set
35
- * `skipClientNav` to true avoid using client-side nav entirely.
36
- *
37
- * NOTE: All URLs containing a protocol are considered external, e.g.
38
- * https://khanacademy.org/math/algebra/eval-exprs will trigger a full
39
- * page reload.
40
- */
41
- skipClientNav?: boolean;
42
31
  /**
43
32
  * Test ID used for e2e testing.
44
33
  */
@@ -73,9 +62,26 @@ type ActionProps = {
73
62
  * @ignore
74
63
  */
75
64
  style?: StyleType;
65
+ /**
66
+ * Inherited from WB Cell.
67
+ */
68
+ /**
69
+ * Adds a horizontal rule at the bottom of the cell that can be used to
70
+ * separate items within ActionMenu instances. Defaults to `none`.
71
+ */
72
+ horizontalRule?: CompactCellProps["horizontalRule"];
73
+ /**
74
+ * Optional left accessory to display in the `ActionItem` element.
75
+ */
76
+ leftAccessory?: CompactCellProps["leftAccessory"];
77
+ /**
78
+ * Optional right accessory to display in the `ActionItem` element.
79
+ */
80
+ rightAccessory?: CompactCellProps["rightAccessory"];
76
81
  };
77
82
  type DefaultProps = {
78
83
  disabled: ActionProps["disabled"];
84
+ horizontalRule: ActionProps["horizontalRule"];
79
85
  indent: ActionProps["indent"];
80
86
  role: ActionProps["role"];
81
87
  };
@@ -88,7 +94,6 @@ export default class ActionItem extends React.Component<ActionProps> {
88
94
  static isClassOf(instance: React.ReactElement<any>): boolean;
89
95
  static defaultProps: DefaultProps;
90
96
  static __IS_ACTION_ITEM__: boolean;
91
- renderClickableBehavior(router: any): React.ReactNode;
92
97
  render(): React.ReactNode;
93
98
  }
94
99
  export {};
package/dist/es/index.js CHANGED
@@ -1,13 +1,12 @@
1
1
  import * as React from 'react';
2
2
  import { StyleSheet } from 'aphrodite';
3
- import { Link } from 'react-router-dom';
4
- import { __RouterContext } from 'react-router';
3
+ import { CompactCell } from '@khanacademy/wonder-blocks-cell';
5
4
  import Color, { mix, fade, SemanticColor } from '@khanacademy/wonder-blocks-color';
6
5
  import Spacing from '@khanacademy/wonder-blocks-spacing';
7
6
  import { LabelMedium, LabelLarge } from '@khanacademy/wonder-blocks-typography';
8
- import { getClickableBehavior, isClientSideUrl, ClickableBehavior } from '@khanacademy/wonder-blocks-clickable';
9
- import { addStyle, View } from '@khanacademy/wonder-blocks-core';
10
7
  import { tokens } from '@khanacademy/wonder-blocks-theming';
8
+ import { View, addStyle } from '@khanacademy/wonder-blocks-core';
9
+ import { getClickableBehavior, ClickableBehavior } from '@khanacademy/wonder-blocks-clickable';
11
10
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
12
11
  import checkIcon from '@phosphor-icons/core/bold/check-bold.svg';
13
12
  import * as ReactDOM from 'react-dom';
@@ -18,6 +17,7 @@ import { Popper } from 'react-popper';
18
17
  import { maybeGetPortalMountedModalHostElement } from '@khanacademy/wonder-blocks-modal';
19
18
  import { Strut } from '@khanacademy/wonder-blocks-layout';
20
19
  import caretDownIcon from '@phosphor-icons/core/bold/caret-down-bold.svg';
20
+ import { __RouterContext } from 'react-router';
21
21
 
22
22
  function _extends() {
23
23
  _extends = Object.assign ? Object.assign.bind() : function (target) {
@@ -69,111 +69,91 @@ const {
69
69
  offBlack: offBlack$2,
70
70
  offBlack32: offBlack32$3
71
71
  } = Color;
72
- const StyledAnchor = addStyle("a");
73
- const StyledButton$2 = addStyle("button");
74
- const StyledLink = addStyle(Link);
75
72
  class ActionItem extends React.Component {
76
73
  static isClassOf(instance) {
77
74
  return instance && instance.type && instance.type.__IS_ACTION_ITEM__;
78
75
  }
79
- renderClickableBehavior(router) {
76
+ render() {
80
77
  const {
81
- skipClientNav,
82
78
  disabled,
79
+ horizontalRule,
83
80
  href,
84
81
  target,
85
82
  indent,
86
83
  label,
87
84
  lang,
85
+ leftAccessory,
86
+ rightAccessory,
88
87
  onClick,
89
88
  role,
90
89
  style,
91
90
  testId
92
91
  } = this.props;
93
- const ClickableBehavior = getClickableBehavior(href, skipClientNav, router);
94
- return React.createElement(ClickableBehavior, {
92
+ const defaultStyle = [styles$8.wrapper, style];
93
+ const labelComponent = typeof label === "string" ? React.createElement(LabelMedium, {
94
+ lang: lang,
95
+ style: styles$8.label
96
+ }, label) : React.cloneElement(label, _extends({
97
+ lang,
98
+ style: styles$8.label
99
+ }, label.props));
100
+ return React.createElement(CompactCell, {
95
101
  disabled: disabled,
96
- onClick: onClick,
97
- href: href,
102
+ horizontalRule: horizontalRule,
103
+ rootStyle: defaultStyle,
104
+ leftAccessory: leftAccessory,
105
+ rightAccessory: rightAccessory,
106
+ style: [styles$8.shared, indent && styles$8.indent],
98
107
  role: role,
99
- target: target
100
- }, (state, childrenProps) => {
101
- const {
102
- pressed,
103
- hovered,
104
- focused
105
- } = state;
106
- const defaultStyle = [styles$8.shared, disabled && styles$8.disabled, !disabled && (pressed ? styles$8.active : (hovered || focused) && styles$8.focus), style];
107
- const props = _extends({
108
- "data-test-id": testId,
109
- disabled,
110
- role,
111
- style: [defaultStyle]
112
- }, childrenProps);
113
- const children = React.createElement(React.Fragment, null, React.createElement(LabelMedium, {
114
- lang: lang,
115
- style: [indent && styles$8.indent, styles$8.label]
116
- }, label));
117
- if (href && !disabled) {
118
- return router && !skipClientNav && isClientSideUrl(href) ? React.createElement(StyledLink, _extends({}, props, {
119
- to: href
120
- }), children) : React.createElement(StyledAnchor, _extends({}, props, {
121
- href: href,
122
- target: target
123
- }), children);
124
- } else {
125
- return React.createElement(StyledButton$2, _extends({
126
- type: "button"
127
- }, props, {
128
- disabled: disabled
129
- }), children);
130
- }
108
+ testId: testId,
109
+ title: labelComponent,
110
+ href: href,
111
+ target: target,
112
+ onClick: onClick
131
113
  });
132
114
  }
133
- render() {
134
- return React.createElement(__RouterContext.Consumer, null, router => this.renderClickableBehavior(router));
135
- }
136
115
  }
137
116
  ActionItem.defaultProps = {
138
117
  disabled: false,
118
+ horizontalRule: "none",
139
119
  indent: false,
140
120
  role: "menuitem"
141
121
  };
142
122
  ActionItem.__IS_ACTION_ITEM__ = true;
143
123
  const styles$8 = StyleSheet.create({
124
+ wrapper: {
125
+ minHeight: DROPDOWN_ITEM_HEIGHT,
126
+ touchAction: "manipulation",
127
+ ":focus": {
128
+ borderRadius: Spacing.xxxSmall_4,
129
+ outline: `${Spacing.xxxxSmall_2}px solid ${Color.blue}`,
130
+ outlineOffset: -Spacing.xxxxSmall_2
131
+ },
132
+ [":hover[aria-disabled=false]"]: {
133
+ color: white$3,
134
+ background: blue$2
135
+ },
136
+ ["@media not (hover: hover)"]: {
137
+ [":hover[aria-disabled=false]"]: {
138
+ color: white$3,
139
+ background: offBlack$2
140
+ }
141
+ },
142
+ [":active[aria-disabled=false]"]: {
143
+ color: mix(fade(blue$2, 0.32), white$3),
144
+ background: mix(offBlack32$3, blue$2)
145
+ }
146
+ },
144
147
  shared: {
145
- background: white$3,
146
- color: offBlack$2,
147
- textDecoration: "none",
148
- border: "none",
149
- outline: "none",
150
- flexDirection: "row",
151
- alignItems: "center",
152
- display: "flex",
153
- height: DROPDOWN_ITEM_HEIGHT,
154
148
  minHeight: DROPDOWN_ITEM_HEIGHT,
155
- paddingLeft: Spacing.medium_16,
156
- paddingRight: Spacing.medium_16,
157
- touchAction: "manipulation"
149
+ height: DROPDOWN_ITEM_HEIGHT
158
150
  },
159
151
  label: {
160
152
  whiteSpace: "nowrap",
161
153
  userSelect: "none"
162
154
  },
163
155
  indent: {
164
- marginLeft: Spacing.medium_16
165
- },
166
- focus: {
167
- color: white$3,
168
- background: blue$2
169
- },
170
- active: {
171
- color: mix(fade(blue$2, 0.32), white$3),
172
- background: mix(offBlack32$3, blue$2)
173
- },
174
- disabled: {
175
- color: offBlack32$3,
176
- cursor: "default"
156
+ paddingLeft: Spacing.medium_16 * 2
177
157
  }
178
158
  });
179
159
 
package/dist/index.js CHANGED
@@ -4,14 +4,13 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var React = require('react');
6
6
  var aphrodite = require('aphrodite');
7
- var reactRouterDom = require('react-router-dom');
8
- var reactRouter = require('react-router');
7
+ var wonderBlocksCell = require('@khanacademy/wonder-blocks-cell');
9
8
  var Color = require('@khanacademy/wonder-blocks-color');
10
9
  var Spacing = require('@khanacademy/wonder-blocks-spacing');
11
10
  var wonderBlocksTypography = require('@khanacademy/wonder-blocks-typography');
12
- var wonderBlocksClickable = require('@khanacademy/wonder-blocks-clickable');
13
- var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
14
11
  var wonderBlocksTheming = require('@khanacademy/wonder-blocks-theming');
12
+ var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
13
+ var wonderBlocksClickable = require('@khanacademy/wonder-blocks-clickable');
15
14
  var wonderBlocksIcon = require('@khanacademy/wonder-blocks-icon');
16
15
  var checkIcon = require('@phosphor-icons/core/bold/check-bold.svg');
17
16
  var ReactDOM = require('react-dom');
@@ -22,6 +21,7 @@ var reactPopper = require('react-popper');
22
21
  var wonderBlocksModal = require('@khanacademy/wonder-blocks-modal');
23
22
  var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
24
23
  var caretDownIcon = require('@phosphor-icons/core/bold/caret-down-bold.svg');
24
+ var reactRouter = require('react-router');
25
25
 
26
26
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
27
27
 
@@ -101,111 +101,91 @@ const {
101
101
  offBlack: offBlack$2,
102
102
  offBlack32: offBlack32$3
103
103
  } = Color__default["default"];
104
- const StyledAnchor = wonderBlocksCore.addStyle("a");
105
- const StyledButton$2 = wonderBlocksCore.addStyle("button");
106
- const StyledLink = wonderBlocksCore.addStyle(reactRouterDom.Link);
107
104
  class ActionItem extends React__namespace.Component {
108
105
  static isClassOf(instance) {
109
106
  return instance && instance.type && instance.type.__IS_ACTION_ITEM__;
110
107
  }
111
- renderClickableBehavior(router) {
108
+ render() {
112
109
  const {
113
- skipClientNav,
114
110
  disabled,
111
+ horizontalRule,
115
112
  href,
116
113
  target,
117
114
  indent,
118
115
  label,
119
116
  lang,
117
+ leftAccessory,
118
+ rightAccessory,
120
119
  onClick,
121
120
  role,
122
121
  style,
123
122
  testId
124
123
  } = this.props;
125
- const ClickableBehavior = wonderBlocksClickable.getClickableBehavior(href, skipClientNav, router);
126
- return React__namespace.createElement(ClickableBehavior, {
124
+ const defaultStyle = [styles$8.wrapper, style];
125
+ const labelComponent = typeof label === "string" ? React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
126
+ lang: lang,
127
+ style: styles$8.label
128
+ }, label) : React__namespace.cloneElement(label, _extends({
129
+ lang,
130
+ style: styles$8.label
131
+ }, label.props));
132
+ return React__namespace.createElement(wonderBlocksCell.CompactCell, {
127
133
  disabled: disabled,
128
- onClick: onClick,
129
- href: href,
134
+ horizontalRule: horizontalRule,
135
+ rootStyle: defaultStyle,
136
+ leftAccessory: leftAccessory,
137
+ rightAccessory: rightAccessory,
138
+ style: [styles$8.shared, indent && styles$8.indent],
130
139
  role: role,
131
- target: target
132
- }, (state, childrenProps) => {
133
- const {
134
- pressed,
135
- hovered,
136
- focused
137
- } = state;
138
- const defaultStyle = [styles$8.shared, disabled && styles$8.disabled, !disabled && (pressed ? styles$8.active : (hovered || focused) && styles$8.focus), style];
139
- const props = _extends({
140
- "data-test-id": testId,
141
- disabled,
142
- role,
143
- style: [defaultStyle]
144
- }, childrenProps);
145
- const children = React__namespace.createElement(React__namespace.Fragment, null, React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
146
- lang: lang,
147
- style: [indent && styles$8.indent, styles$8.label]
148
- }, label));
149
- if (href && !disabled) {
150
- return router && !skipClientNav && wonderBlocksClickable.isClientSideUrl(href) ? React__namespace.createElement(StyledLink, _extends({}, props, {
151
- to: href
152
- }), children) : React__namespace.createElement(StyledAnchor, _extends({}, props, {
153
- href: href,
154
- target: target
155
- }), children);
156
- } else {
157
- return React__namespace.createElement(StyledButton$2, _extends({
158
- type: "button"
159
- }, props, {
160
- disabled: disabled
161
- }), children);
162
- }
140
+ testId: testId,
141
+ title: labelComponent,
142
+ href: href,
143
+ target: target,
144
+ onClick: onClick
163
145
  });
164
146
  }
165
- render() {
166
- return React__namespace.createElement(reactRouter.__RouterContext.Consumer, null, router => this.renderClickableBehavior(router));
167
- }
168
147
  }
169
148
  ActionItem.defaultProps = {
170
149
  disabled: false,
150
+ horizontalRule: "none",
171
151
  indent: false,
172
152
  role: "menuitem"
173
153
  };
174
154
  ActionItem.__IS_ACTION_ITEM__ = true;
175
155
  const styles$8 = aphrodite.StyleSheet.create({
156
+ wrapper: {
157
+ minHeight: DROPDOWN_ITEM_HEIGHT,
158
+ touchAction: "manipulation",
159
+ ":focus": {
160
+ borderRadius: Spacing__default["default"].xxxSmall_4,
161
+ outline: `${Spacing__default["default"].xxxxSmall_2}px solid ${Color__default["default"].blue}`,
162
+ outlineOffset: -Spacing__default["default"].xxxxSmall_2
163
+ },
164
+ [":hover[aria-disabled=false]"]: {
165
+ color: white$3,
166
+ background: blue$2
167
+ },
168
+ ["@media not (hover: hover)"]: {
169
+ [":hover[aria-disabled=false]"]: {
170
+ color: white$3,
171
+ background: offBlack$2
172
+ }
173
+ },
174
+ [":active[aria-disabled=false]"]: {
175
+ color: Color.mix(Color.fade(blue$2, 0.32), white$3),
176
+ background: Color.mix(offBlack32$3, blue$2)
177
+ }
178
+ },
176
179
  shared: {
177
- background: white$3,
178
- color: offBlack$2,
179
- textDecoration: "none",
180
- border: "none",
181
- outline: "none",
182
- flexDirection: "row",
183
- alignItems: "center",
184
- display: "flex",
185
- height: DROPDOWN_ITEM_HEIGHT,
186
180
  minHeight: DROPDOWN_ITEM_HEIGHT,
187
- paddingLeft: Spacing__default["default"].medium_16,
188
- paddingRight: Spacing__default["default"].medium_16,
189
- touchAction: "manipulation"
181
+ height: DROPDOWN_ITEM_HEIGHT
190
182
  },
191
183
  label: {
192
184
  whiteSpace: "nowrap",
193
185
  userSelect: "none"
194
186
  },
195
187
  indent: {
196
- marginLeft: Spacing__default["default"].medium_16
197
- },
198
- focus: {
199
- color: white$3,
200
- background: blue$2
201
- },
202
- active: {
203
- color: Color.mix(Color.fade(blue$2, 0.32), white$3),
204
- background: Color.mix(offBlack32$3, blue$2)
205
- },
206
- disabled: {
207
- color: offBlack32$3,
208
- cursor: "default"
188
+ paddingLeft: Spacing__default["default"].medium_16 * 2
209
189
  }
210
190
  });
211
191
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-dropdown",
3
- "version": "3.1.10",
3
+ "version": "4.0.0",
4
4
  "design": "v1",
5
5
  "description": "Dropdown variants for Wonder Blocks.",
6
6
  "main": "dist/index.js",
@@ -16,13 +16,14 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@babel/runtime": "^7.18.6",
19
+ "@khanacademy/wonder-blocks-cell": "^3.1.0",
19
20
  "@khanacademy/wonder-blocks-clickable": "^4.0.11",
20
21
  "@khanacademy/wonder-blocks-color": "^3.0.0",
21
22
  "@khanacademy/wonder-blocks-core": "^6.3.0",
22
23
  "@khanacademy/wonder-blocks-icon": "^4.0.0",
23
24
  "@khanacademy/wonder-blocks-layout": "^2.0.24",
24
- "@khanacademy/wonder-blocks-modal": "^4.0.37",
25
- "@khanacademy/wonder-blocks-search-field": "^2.1.24",
25
+ "@khanacademy/wonder-blocks-modal": "^4.0.38",
26
+ "@khanacademy/wonder-blocks-search-field": "^2.1.25",
26
27
  "@khanacademy/wonder-blocks-spacing": "^4.0.1",
27
28
  "@khanacademy/wonder-blocks-theming": "1.2.1",
28
29
  "@khanacademy/wonder-blocks-timing": "^4.0.2",
@@ -2,6 +2,10 @@ import * as React from "react";
2
2
  import {render, screen} from "@testing-library/react";
3
3
  import * as ReactRouterDOM from "react-router-dom";
4
4
 
5
+ import {HeadingSmall} from "@khanacademy/wonder-blocks-typography";
6
+ import {PhosphorIcon} from "@khanacademy/wonder-blocks-icon";
7
+
8
+ import plusIcon from "@phosphor-icons/core/regular/plus.svg";
5
9
  import ActionItem from "../action-item";
6
10
 
7
11
  jest.mock("react-router-dom", () => ({
@@ -18,7 +22,10 @@ describe("ActionItem", () => {
18
22
  render(<ActionItem href="/foo" label="Example" disabled={true} />);
19
23
 
20
24
  // Assert
21
- expect(screen.getByRole("menuitem")).toBeDisabled();
25
+ expect(screen.getByRole("menuitem")).toHaveAttribute(
26
+ "aria-disabled",
27
+ "true",
28
+ );
22
29
  });
23
30
 
24
31
  it("should render an anchor if there's no router", () => {
@@ -56,4 +63,33 @@ describe("ActionItem", () => {
56
63
  // Assert
57
64
  expect(screen.getByText("Español")).toHaveAttribute("lang", "es");
58
65
  });
66
+
67
+ it("should allow passing an accessory", () => {
68
+ // Arrange
69
+
70
+ // Act
71
+ render(
72
+ <ActionItem
73
+ label="ActionItem"
74
+ leftAccessory={<PhosphorIcon icon={plusIcon} role="img" />}
75
+ />,
76
+ );
77
+
78
+ // Assert
79
+ expect(screen.getByRole("img")).toBeInTheDocument();
80
+ });
81
+
82
+ it("should allow passing a custom label", () => {
83
+ // Arrange
84
+
85
+ // Act
86
+ render(
87
+ <ActionItem
88
+ label={<HeadingSmall>A heading as an item</HeadingSmall>}
89
+ />,
90
+ );
91
+
92
+ // Assert
93
+ expect(screen.getByRole("heading")).toBeInTheDocument();
94
+ });
59
95
  });