@sproutsocial/seeds-react-menu 0.2.1 → 0.2.3

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.
Files changed (44) hide show
  1. package/.eslintrc.js +2 -6
  2. package/.turbo/turbo-build.log +10 -10
  3. package/CHANGELOG.md +13 -0
  4. package/dist/esm/index.js +12 -12
  5. package/dist/esm/index.js.map +1 -1
  6. package/dist/index.d.mts +34 -12
  7. package/dist/index.d.ts +34 -12
  8. package/dist/index.js +7 -11
  9. package/dist/index.js.map +1 -1
  10. package/package.json +3 -5
  11. package/src/ActionMenu/ActionMenu.tsx +4 -19
  12. package/src/ActionMenu/ActionMenuTypes.ts +12 -0
  13. package/src/ActionMenu/index.ts +1 -0
  14. package/src/{ComboBox/ComboBox.feature → Autocomplete/Autocomplete.feature} +16 -16
  15. package/src/Autocomplete/Autocomplete.tsx +65 -0
  16. package/src/Autocomplete/AutocompleteTypes.ts +8 -0
  17. package/src/Autocomplete/__tests__/Autocomplete.test.tsx +113 -0
  18. package/src/Autocomplete/__tests__/Autocomplete.typetest.tsx +78 -0
  19. package/src/Autocomplete/index.ts +2 -0
  20. package/src/MenuContent/MenuContent.tsx +1 -1
  21. package/src/MenuContent/MenuContentTypes.ts +2 -1
  22. package/src/MenuContext.tsx +71 -10
  23. package/src/MenuGroup/MenuGroupTypes.ts +0 -1
  24. package/src/MenuGroup/__tests__/MenuGroup.test.tsx +0 -1
  25. package/src/MenuHeader/__tests__/MenuHeader.typetest.tsx +0 -1
  26. package/src/MenuItem/MenuItem.tsx +0 -4
  27. package/src/MenuItem/__tests__/MenuItem.test.tsx +4 -1
  28. package/src/MenuItem/styles.tsx +1 -1
  29. package/src/MenuItem/useOptionProps.tsx +0 -1
  30. package/src/MenuRoot/MenuRoot.tsx +2 -2
  31. package/src/MenuRoot/__tests__/MenuRoot.test.tsx +1 -2
  32. package/src/MultiSelectMenu/MultiSelectMenu.tsx +0 -1
  33. package/src/MultiSelectMenu/MultiSelectMenuTypes.ts +5 -1
  34. package/src/MultiSelectMenu/__tests__/MultiSelectMenu.test.tsx +5 -2
  35. package/src/MultiSelectMenu/index.ts +1 -0
  36. package/src/SingleSelectMenu/SingleSelectMenu.tsx +0 -1
  37. package/src/SingleSelectMenu/SingleSelectMenuTypes.ts +2 -3
  38. package/src/hooks/useItemsFromChildren.tsx +7 -5
  39. package/src/index.ts +1 -0
  40. package/src/reducers/useComboboxStateReducer.tsx +26 -0
  41. package/src/types.ts +2 -5
  42. package/tsconfig.json +1 -1
  43. package/.eslintignore +0 -6
  44. package/src/ComboBox/TokenInput.feature +0 -84
@@ -23,7 +23,6 @@ export const MenuItemContainer = styled.li`
23
23
  list-style-type: none;
24
24
  margin-left: ${({ theme }) => theme.space[300]};
25
25
  margin-right: ${({ theme }) => theme.space[300]};
26
- transition: all ${({ theme }) => theme.duration["fast"]};
27
26
 
28
27
  &:first-child {
29
28
  margin-top: ${({ theme }) => theme.space[300]};
@@ -49,6 +48,7 @@ export const StyledMenuItem = styled.div<{ $highlighted: boolean }>`
49
48
  padding: ${({ theme }) => theme.space[300]};
50
49
  outline: 0;
51
50
  ${({ theme }) => theme.typography[200]};
51
+ transition: all ${({ theme }) => theme.duration["fast"]};
52
52
 
53
53
  /* Highlighted */
54
54
  ${({ $highlighted }) => $highlighted && highlightedStyles}
@@ -85,7 +85,6 @@ export const useOptionProps = ({
85
85
  () =>
86
86
  selectedItem?.id === id ||
87
87
  !!selectedItems?.find((item) => item?.id === id),
88
- // eslint-disable-next-line react-hooks/exhaustive-deps
89
88
  [selectedItem, selectedItems]
90
89
  );
91
90
 
@@ -1,11 +1,10 @@
1
- import React, { useCallback } from "react";
1
+ import React, { useCallback, useRef } from "react";
2
2
  import styled from "styled-components";
3
3
  import { Popout, type TypePopoutProps } from "@sproutsocial/seeds-react-popout";
4
4
 
5
5
  import { MenuProvider, type TypeMenuProviderProps } from "../MenuContext";
6
6
 
7
7
  export interface TypeMenuRootOwnProps {
8
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
8
  menuToggleElement: React.ReactElement<any>;
10
9
  popoutProps?: Partial<
11
10
  Omit<TypePopoutProps, "isOpen" | "setIsOpen" | "content" | "children">
@@ -46,6 +45,7 @@ export const MenuRoot = ({
46
45
  return (
47
46
  <MenuProvider {...contextProps}>
48
47
  <Popout
48
+ menuPopout
49
49
  isOpen={!!isOpen}
50
50
  setIsOpen={setIsOpen}
51
51
  content={
@@ -5,6 +5,7 @@ import {
5
5
  screen,
6
6
  waitFor,
7
7
  } from "@sproutsocial/seeds-react-testing-library";
8
+ import { Box } from "@sproutsocial/seeds-react-box";
8
9
  import { Button } from "@sproutsocial/seeds-react-button";
9
10
  import {
10
11
  MenuRoot,
@@ -17,7 +18,6 @@ import {
17
18
  type TypeMenuContextProps,
18
19
  } from "../../index";
19
20
 
20
- // eslint-disable-next-line react/display-name
21
21
  jest.mock("@sproutsocial/seeds-react-portal", () => ({ children }) => (
22
22
  <div>{children}</div>
23
23
  ));
@@ -197,7 +197,6 @@ describe("MenuRoot", () => {
197
197
  const { user } = render(
198
198
  <MenuRoot
199
199
  menuToggleElement={<CustomToggleElement />}
200
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
201
200
  // @ts-ignore - mock contextProps may be imperfect
202
201
  getToggleButtonProps={() => ({ onClick: mockToggleClick })}
203
202
  >
@@ -82,7 +82,6 @@ export const MultiSelectMenu = ({
82
82
  defaultHighlightedIndex: 0,
83
83
  stateReducer: (state, actionAndChanges) => {
84
84
  let newState: Partial<UseSelectState<TypeDefaultItemProps>>;
85
- // eslint-disable-next-line react-hooks/rules-of-hooks
86
85
  newState = useSelectStateReducer(state, actionAndChanges);
87
86
  newState = selectReducerForMultiSelect(state, {
88
87
  ...actionAndChanges,
@@ -1,8 +1,12 @@
1
1
  import type { TypeMenuRootProps, TypeMenuRootOwnProps } from "../MenuRoot";
2
- import type { TypeDownshiftMultiSelectProps } from "../MenuContext";
2
+ import type {
3
+ TypeDownshiftSelectProps,
4
+ TypeDownshiftMultiSelectProps,
5
+ } from "../MenuContext";
3
6
 
4
7
  export interface TypeMultiSelectMenuProps
5
8
  extends TypeMenuRootOwnProps,
9
+ TypeDownshiftSelectProps,
6
10
  TypeDownshiftMultiSelectProps {
7
11
  children: TypeMenuRootProps["children"];
8
12
  }
@@ -1,5 +1,3 @@
1
- /* eslint-disable testing-library/prefer-find-by */
2
- /* eslint-disable testing-library/no-wait-for-multiple-assertions */
3
1
  /* eslint-disable testing-library/no-unnecessary-act */
4
2
  import {
5
3
  MultiSelectMenu,
@@ -11,6 +9,7 @@ import {
11
9
  import {
12
10
  render,
13
11
  screen,
12
+ cleanup,
14
13
  waitFor,
15
14
  } from "@sproutsocial/seeds-react-testing-library";
16
15
 
@@ -22,6 +21,10 @@ const menuItems = [
22
21
  ];
23
22
 
24
23
  describe("MultiSelectMenu", () => {
24
+ afterEach(() => {
25
+ cleanup();
26
+ });
27
+
25
28
  it("should render the MenuItems when provided as children", async () => {
26
29
  const { user } = render(
27
30
  <MultiSelectMenu
@@ -1 +1,2 @@
1
1
  export * from "./MultiSelectMenu";
2
+ export * from "./MultiSelectMenuTypes";
@@ -36,7 +36,6 @@ export const SingleSelectMenu = ({
36
36
  itemToString,
37
37
  stateReducer: (state, actionAndChanges) => {
38
38
  let newState: Partial<UseSelectState<TypeDefaultItemProps>>;
39
- // eslint-disable-next-line react-hooks/rules-of-hooks, prefer-const
40
39
  newState = useSelectStateReducer(state, actionAndChanges);
41
40
  return externalStateReducer
42
41
  ? externalStateReducer(state, {
@@ -1,9 +1,8 @@
1
- import type { UseSelectProps } from "downshift";
2
1
  import type { TypeMenuRootProps, TypeMenuRootOwnProps } from "../MenuRoot";
3
- import type { TypeDefaultItemProps } from "../types";
2
+ import type { TypeDownshiftSelectProps } from "../MenuContext";
4
3
 
5
4
  export interface TypeSingleSelectMenuProps
6
5
  extends TypeMenuRootOwnProps,
7
- Partial<UseSelectProps<TypeDefaultItemProps>> {
6
+ TypeDownshiftSelectProps {
8
7
  children: TypeMenuRootProps["children"];
9
8
  }
@@ -1,10 +1,14 @@
1
1
  import React, { type ReactNode, useMemo } from "react";
2
- import type { TypeItemElements } from "../types";
2
+ import type { TypeMenuItemProps } from "../MenuItem";
3
3
  import type { TypeMenuContext } from "../MenuContext";
4
4
 
5
5
  export interface TypeUseItemsFromChildrenProps {
6
6
  children: ReactNode;
7
7
  }
8
+ export interface TypeItemElements {
9
+ element: React.ReactElement<TypeMenuItemProps>;
10
+ parents: readonly React.ReactNode[];
11
+ }
8
12
 
9
13
  // https://stackoverflow.com/questions/69119016/find-specific-children-components-regardless-of-depth-in-react
10
14
  const walkAllChildren = (
@@ -16,7 +20,6 @@ const walkAllChildren = (
16
20
  // console.log("element", element);
17
21
  callback(element, parents);
18
22
  const newParents = [...parents, element];
19
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
23
  const children = (element as any).props?.children;
21
24
  React.Children.toArray(children).forEach((child) => {
22
25
  walk(child, newParents);
@@ -60,13 +63,12 @@ export const useItemsFromChildren = ({
60
63
  const allMenuItemElements = getAllMenuItems(children);
61
64
  const menuItemsMap: TypeMenuContext["itemsMap"] = {};
62
65
  const allMenuItems: TypeMenuContext["items"] = allMenuItemElements.map(
63
- ({ element, parents }, index) => {
66
+ ({ element }, index) => {
64
67
  const item = {
65
68
  id: element.props.id,
66
69
  disabled: element.props.disabled,
67
70
  index,
68
- element,
69
- parents,
71
+ menuItemProps: element.props,
70
72
  };
71
73
  menuItemsMap[`${item.id}`] = item;
72
74
  return item;
package/src/index.ts CHANGED
@@ -17,3 +17,4 @@ export * from "./MultiSelectMenu";
17
17
  export * from "./MenuContext";
18
18
  export * from "./hooks/useItemsFromChildren";
19
19
  export * from "./reducers/useSelectStateReducer";
20
+ export * from "./types";
@@ -0,0 +1,26 @@
1
+ import { useCombobox } from "downshift";
2
+ import type { UseComboboxProps } from "downshift";
3
+ import type { TypeDefaultItemProps } from "../types";
4
+
5
+ export const useComboboxStateReducer: Required<
6
+ UseComboboxProps<TypeDefaultItemProps>
7
+ >["stateReducer"] = (state, actionAndChanges) => {
8
+ const { type, changes } = actionAndChanges;
9
+ switch (type) {
10
+ /** If downshift is blurred by tabbing within the menu, stay open */
11
+ // case useCombobox.stateChangeTypes.InputBlur:
12
+ // return {
13
+ // ...changes,
14
+ // // keep menu open on blur
15
+ // isOpen: true,
16
+ // };
17
+
18
+ /** Return standard change payload */
19
+ default:
20
+ return changes;
21
+ }
22
+ };
23
+
24
+ export const defaultUseComboboxProps = {
25
+ isItemDisabled: (item) => !!item.disabled,
26
+ };
package/src/types.ts CHANGED
@@ -1,13 +1,10 @@
1
1
  import type { TypeMenuItemProps } from "./MenuItem";
2
2
 
3
- export interface TypeItemElements {
4
- element: React.ReactElement<TypeMenuItemProps>;
5
- parents: readonly React.ReactNode[];
6
- }
7
- export interface TypeDefaultItemProps extends Partial<TypeItemElements> {
3
+ export interface TypeDefaultItemProps {
8
4
  id: string;
9
5
  index: number;
10
6
  disabled?: boolean;
7
+ menuItemProps?: TypeMenuItemProps;
11
8
  }
12
9
 
13
10
  export type TypeNotEmptyChildren =
package/tsconfig.json CHANGED
@@ -8,5 +8,5 @@
8
8
  "verbatimModuleSyntax": true
9
9
  },
10
10
  "include": ["src/**/*"],
11
- "exclude": ["node_modules", "dist", "coverage"]
11
+ "exclude": ["node_modules", "dist"]
12
12
  }
package/.eslintignore DELETED
@@ -1,6 +0,0 @@
1
- # Node modules
2
- node_modules/
3
-
4
- # Build output
5
- dist/
6
- coverage/
@@ -1,84 +0,0 @@
1
- Feature: TokenInput
2
-
3
- A component that allows users to search, select, and manage tokens with autocomplete functionality.
4
-
5
- Rule: User behavior
6
-
7
- Background:
8
- Given an end user is using the component
9
-
10
- Scenario: User can add tokens
11
- Given the ComboBox accepts new values
12
- When text is typed
13
- And a delimiter key is pressed
14
- Then a new token is created from the input text
15
-
16
- Scenario: User can not add tokens
17
- Given the ComboBox does not accept new values
18
- When text is typed
19
- And a delimiter key is pressed
20
- Then no new token is added
21
-
22
- Scenario: User can remove tokens manually
23
- Given the ComboBox has tokens
24
- When token's remove button is clicked
25
- Then the token is removed
26
-
27
- Scenario: User can remove tokens with backspace
28
- Given the ComboBox has tokens
29
- When backspace is hit
30
- Then the token is removed
31
-
32
- Scenario: User can navigate tokens via keyboard
33
- Given the ComboBox has tokens
34
- And the Combobox Input has focus
35
- When the tokens are navigated using arrow keys
36
- Then the activeToken callback fires
37
-
38
- Scenario: Component handles token addition
39
- When a new token is added
40
- Then the onAddToken callback fires
41
-
42
- Scenario: Component handles token removal
43
- Given the ComboBox is rendered with tokens
44
- When a token is removed
45
- Then the onRemoveToken callback fires
46
-
47
- Scenario: Component handles token click
48
- When a token is clicked
49
- Then the onClickToken callback fires
50
-
51
- Scenario: Component handles input change
52
- When the input value changes
53
- Then the onChange callback fires
54
-
55
- Rule: API
56
- Background:
57
- Given an engineer is developing using the component
58
-
59
- Scenario: TokenInput extends Combobox
60
- When Combobox features are used in TokenInput
61
- Then the TokenInput responds as a Combobox would
62
-
63
- Scenario: ComboBox accepts tokens
64
- When a token component is passed to the Combobox
65
- And an item is selected
66
- Then the token is rendered in the input
67
-
68
- Scenario: ComboBox has a maximum token length
69
- When 'tokenMaxLength' is set to <value>
70
- Then the token string size is limited to <value>
71
-
72
- Scenario Outline: ComboBox checks its types
73
- When <propName> is passed a value
74
- Then ComboBox expects it to be of type <propType>
75
-
76
- Examples: Prop Types
77
- | activeToken | string |
78
- | tokens | array of { id: string, iconName?: EnumIconNames, value: string, valid?: boolean } |
79
- | onChangeTokens | function |
80
- | onAddToken | function |
81
- | onRemoveToken | function |
82
- | onClickToken | function |
83
- | maxLength | number |
84
- | tokenMaxLength | number |