@basic-ui/core 0.0.45 → 0.0.47

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 (37) hide show
  1. package/build/cjs/index.js +85 -10
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/Menu/ContextMenuTrigger.d.ts +11 -0
  4. package/build/esm/Menu/ContextMenuTrigger.js +51 -0
  5. package/build/esm/Menu/ContextMenuTrigger.js.map +1 -0
  6. package/build/esm/Menu/Menu.js +11 -2
  7. package/build/esm/Menu/Menu.js.map +1 -1
  8. package/build/esm/Menu/MenuItem.js +0 -1
  9. package/build/esm/Menu/MenuItem.js.map +1 -1
  10. package/build/esm/Menu/MenuList.js +23 -6
  11. package/build/esm/Menu/MenuList.js.map +1 -1
  12. package/build/esm/Menu/MenuPopover.js +6 -2
  13. package/build/esm/Menu/MenuPopover.js.map +1 -1
  14. package/build/esm/Menu/context.d.ts +5 -1
  15. package/build/esm/Menu/context.js.map +1 -1
  16. package/build/esm/Menu/fixtures/countryList.d.ts +1 -0
  17. package/build/esm/Menu/fixtures/countryList.js +2 -0
  18. package/build/esm/Menu/fixtures/countryList.js.map +1 -0
  19. package/build/esm/Menu/index.d.ts +1 -0
  20. package/build/esm/Menu/index.js +1 -0
  21. package/build/esm/Menu/index.js.map +1 -1
  22. package/build/esm/hooks/useControlledState.js +3 -1
  23. package/build/esm/hooks/useControlledState.js.map +1 -1
  24. package/build/tsconfig-build.tsbuildinfo +1 -1
  25. package/package.json +2 -2
  26. package/src/Menu/ContextMenu.story.tsx +73 -0
  27. package/src/Menu/ContextMenuTrigger.tsx +63 -0
  28. package/src/Menu/Menu.story.tsx +2 -2
  29. package/src/Menu/Menu.tsx +14 -1
  30. package/src/Menu/MenuComplex.story.tsx +58 -0
  31. package/src/Menu/MenuItem.tsx +0 -1
  32. package/src/Menu/MenuList.tsx +38 -9
  33. package/src/Menu/MenuPopover.tsx +9 -2
  34. package/src/Menu/context.ts +5 -1
  35. package/src/Menu/fixtures/countryList.ts +198 -0
  36. package/src/Menu/index.ts +1 -0
  37. package/src/hooks/useControlledState.ts +7 -1
@@ -0,0 +1,58 @@
1
+ import type { MouseEvent, ReactNode } from 'react';
2
+
3
+ import { Menu, MenuButton, MenuItem, MenuList, MenuPopover } from '.';
4
+ import { countryList } from './fixtures/countryList';
5
+ import './styles.css';
6
+
7
+ export default {
8
+ title: 'components/Menu/Complex',
9
+ };
10
+
11
+ const Wrapper = ({ children }: { children: ReactNode }) => {
12
+ const handleLinkClick = (e: MouseEvent<HTMLAnchorElement>) => {
13
+ console.log('Clicked ' + e.currentTarget.innerText);
14
+ e.preventDefault();
15
+ };
16
+
17
+ return (
18
+ <div
19
+ style={{
20
+ boxSizing: 'border-box',
21
+ display: 'flex',
22
+ alignItems: 'flex-start',
23
+ justifyContent: 'space-around',
24
+ width: '100%',
25
+ position: 'relative',
26
+ }}
27
+ >
28
+ <a href="#" onClick={handleLinkClick}>
29
+ Link 1
30
+ </a>
31
+ <div style={{ minHeight: 120, width: 100 }}>{children}</div>
32
+ <a href="#" onClick={handleLinkClick}>
33
+ Link 2
34
+ </a>
35
+ </div>
36
+ );
37
+ };
38
+
39
+ const MenuControlled = () => {
40
+ return (
41
+ <Menu>
42
+ <MenuButton>Click me</MenuButton>
43
+ <MenuPopover>
44
+ <MenuList>
45
+ {countryList.map((country) => (
46
+ <MenuItem key={country}>{country}</MenuItem>
47
+ ))}
48
+ </MenuList>
49
+ </MenuPopover>
50
+ </Menu>
51
+ );
52
+ };
53
+
54
+ export const CountryList = () => (
55
+ <Wrapper>
56
+ <MenuControlled />
57
+ </Wrapper>
58
+ );
@@ -59,7 +59,6 @@ export const MenuItem = forwardRef<any, MenuItemProps>(function MenuItem(
59
59
  const handleKeyDown: KeyboardEventHandler<HTMLLIElement> = (e) => {
60
60
  switch (e.key) {
61
61
  case 'Enter':
62
- case ' ':
63
62
  if (!disabled) {
64
63
  handleSelect(e);
65
64
  }
@@ -33,8 +33,17 @@ export const MenuList = forwardRef<HTMLUListElement, MenuListProps>(
33
33
  ...otherProps
34
34
  } = props;
35
35
 
36
- const { menuListIdRef, buttonRef, onChange, openWithArrowKeyRef, open } =
37
- useMenuContext();
36
+ const itemSearchStr = useRef('');
37
+ const itemSearchClearTimeout = useRef<ReturnType<typeof setTimeout>>();
38
+
39
+ const {
40
+ menuListIdRef,
41
+ buttonRef,
42
+ onChange,
43
+ openWithArrowKeyRef,
44
+ open,
45
+ isContextMenu,
46
+ } = useMenuContext();
38
47
 
39
48
  const [navigationItem, setNavigationItem] = useState<
40
49
  HTMLElement | undefined
@@ -89,10 +98,12 @@ export const MenuList = forwardRef<HTMLUListElement, MenuListProps>(
89
98
  useOnClickOutside(
90
99
  menuListRef,
91
100
  (e) => {
101
+ console.log(isContextMenu.current);
92
102
  if (
93
- e.target instanceof HTMLElement &&
94
- e.target !== buttonRef.current &&
95
- !buttonRef.current?.contains(e.target)
103
+ isContextMenu.current ||
104
+ (e.target instanceof HTMLElement &&
105
+ e.target !== buttonRef.current &&
106
+ !buttonRef.current?.contains(e.target))
96
107
  ) {
97
108
  onChange && onChange(e as any, false);
98
109
  }
@@ -107,6 +118,7 @@ export const MenuList = forwardRef<HTMLUListElement, MenuListProps>(
107
118
  case 'Tab': {
108
119
  onChange && onChange(e, false);
109
120
  e.preventDefault(); // prevents focusing on next element, because we will be handling it
121
+ itemSearchStr.current = '';
110
122
  buttonRef.current?.focus();
111
123
  break;
112
124
  }
@@ -115,6 +127,7 @@ export const MenuList = forwardRef<HTMLUListElement, MenuListProps>(
115
127
  case 'ArrowDown':
116
128
  case 'ArrowUp':
117
129
  e.preventDefault();
130
+ itemSearchStr.current = '';
118
131
  const allItems = scope ? scope.current.queryAllNodes(queryScope) : [];
119
132
  const currentIndex = allItems.findIndex((e) => e === navigationItem);
120
133
  if (allItems.length === 0) {
@@ -141,23 +154,39 @@ export const MenuList = forwardRef<HTMLUListElement, MenuListProps>(
141
154
  onNavigate && onNavigate(allItems[nextIndex]);
142
155
  break;
143
156
  default: {
144
- if (e.key.length === 1) {
157
+ if (e.key.length === 1 && !e.ctrlKey && !e.altKey) {
145
158
  // A-Z
146
159
  e.preventDefault();
160
+
161
+ if (
162
+ itemSearchStr.current.length === 0 ||
163
+ itemSearchStr.current.slice(-1) !== e.key
164
+ ) {
165
+ itemSearchStr.current = itemSearchStr.current + e.key;
166
+ }
167
+ clearTimeout(itemSearchClearTimeout.current as any);
168
+ itemSearchClearTimeout.current = setTimeout(() => {
169
+ itemSearchStr.current = '';
170
+ }, 500);
171
+
147
172
  const allItems = scope
148
173
  ? scope.current.queryAllNodes(queryScope)
149
174
  : [];
150
175
  const currentIndex = allItems.findIndex(
151
176
  (e) => e === navigationItem
152
177
  );
153
- const firstLetter = e.key.toLowerCase();
178
+ const searchStr = itemSearchStr.current;
154
179
  let nextIndex = -1;
155
- for (let i = 1; i < allItems.length; i++) {
180
+ for (
181
+ let i = searchStr.length === 1 ? 1 : 0;
182
+ i < allItems.length;
183
+ i++
184
+ ) {
156
185
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
157
186
  const idx = getCircularIndex(currentIndex + i, allItems.length)!;
158
187
  const dom = allItems[idx];
159
188
  const domText = dom.innerText.toLowerCase();
160
- if (domText.length > 0 && domText.charAt(0) === firstLetter) {
189
+ if (domText.length > 0 && domText.startsWith(searchStr)) {
161
190
  nextIndex = idx;
162
191
  break;
163
192
  }
@@ -14,14 +14,21 @@ export interface MenuPopoverProps extends Omit<PopperProps, 'anchorEl'> {
14
14
  export const MenuPopover = forwardRef<HTMLDivElement, MenuPopoverProps>(
15
15
  function MenuPopover(props, forwardedRef) {
16
16
  const { as = 'div', ...otherProps } = props;
17
- const { buttonRef, open } = useMenuContext();
17
+ const { buttonRef, open, offsetFn, isContextMenu } = useMenuContext();
18
18
 
19
19
  if (!open) {
20
20
  return null;
21
21
  }
22
22
 
23
23
  return (
24
- <Popper as={as} ref={forwardedRef} anchorEl={buttonRef} {...otherProps} />
24
+ <Popper
25
+ as={as}
26
+ ref={forwardedRef}
27
+ anchorEl={buttonRef}
28
+ offsetFn={offsetFn}
29
+ placement={isContextMenu.current ? 'bottom-start' : undefined}
30
+ {...otherProps}
31
+ />
25
32
  );
26
33
  }
27
34
  );
@@ -1,3 +1,4 @@
1
+ import type { OffsetsFunction } from '@popperjs/core/lib/modifiers/offset';
1
2
  import type {
2
3
  KeyboardEvent,
3
4
  MouseEvent,
@@ -10,7 +11,7 @@ export type ItemObject = { text: string; value: any; id: string | undefined };
10
11
 
11
12
  // MenuRoot
12
13
  export interface MenuContextProps {
13
- buttonRef: MutableRefObject<HTMLButtonElement | null>;
14
+ buttonRef: MutableRefObject<HTMLElement | null>;
14
15
  menuListIdRef: MutableRefObject<undefined | string>;
15
16
  openWithArrowKeyRef: MutableRefObject<string | null>;
16
17
  onChange?: (
@@ -21,6 +22,9 @@ export interface MenuContextProps {
21
22
  isOpen: boolean
22
23
  ) => void;
23
24
  open: boolean;
25
+ offset: MutableRefObject<[number, number]>;
26
+ offsetFn: OffsetsFunction;
27
+ isContextMenu: MutableRefObject<boolean>;
24
28
  }
25
29
 
26
30
  const menuContext = createContext<MenuContextProps>(null as any);
@@ -0,0 +1,198 @@
1
+ export const countryList = [
2
+ 'Afghanistan',
3
+ 'Albania',
4
+ 'Algeria',
5
+ 'Andorra',
6
+ 'Angola',
7
+ 'Antigua and Barbuda',
8
+ 'Argentina',
9
+ 'Armenia',
10
+ 'Australia',
11
+ 'Austria',
12
+ 'Azerbaijan',
13
+ 'Bahamas',
14
+ 'Bahrain',
15
+ 'Bangladesh',
16
+ 'Barbados',
17
+ 'Belarus',
18
+ 'Belgium',
19
+ 'Belize',
20
+ 'Benin',
21
+ 'Bhutan',
22
+ 'Bolivia',
23
+ 'Bosnia and Herzegovina',
24
+ 'Botswana',
25
+ 'Brazil',
26
+ 'Brunei',
27
+ 'Bulgaria',
28
+ 'Burkina Faso',
29
+ 'Burundi',
30
+ "Côte d'Ivoire",
31
+ 'Cabo Verde',
32
+ 'Cambodia',
33
+ 'Cameroon',
34
+ 'Canada',
35
+ 'Central African Republic',
36
+ 'Central American Republic',
37
+ 'Chad',
38
+ 'Chile',
39
+ 'China',
40
+ 'Colombia',
41
+ 'Comoros',
42
+ 'Congo (Congo-Brazzaville)',
43
+ 'Costa Rica',
44
+ 'Croatia',
45
+ 'Cuba',
46
+ 'Cyprus',
47
+ 'Czechia (Czech Republic)',
48
+ 'Democratic Republic of the Congo',
49
+ 'Denmark',
50
+ 'Djibouti',
51
+ 'Dominica',
52
+ 'Dominican Republic',
53
+ 'Ecuador',
54
+ 'Egypt',
55
+ 'El Salvador',
56
+ 'Equatorial Guinea',
57
+ 'Eritrea',
58
+ 'Estonia',
59
+ 'Eswatini (fmr. "Swaziland")',
60
+ 'Ethiopia',
61
+ 'Fiji',
62
+ 'Finland',
63
+ 'France',
64
+ 'Gabon',
65
+ 'Gambia',
66
+ 'Georgia',
67
+ 'Germany',
68
+ 'Ghana',
69
+ 'Greece',
70
+ 'Grenada',
71
+ 'Guatemala',
72
+ 'Guinea',
73
+ 'Guinea-Bissau',
74
+ 'Guyana',
75
+ 'Haiti',
76
+ 'Holy See',
77
+ 'Honduras',
78
+ 'Hungary',
79
+ 'Iceland',
80
+ 'India',
81
+ 'Indonesia',
82
+ 'Iran',
83
+ 'Iraq',
84
+ 'Ireland',
85
+ 'Israel',
86
+ 'Italy',
87
+ 'Jamaica',
88
+ 'Japan',
89
+ 'Jordan',
90
+ 'Kazakhstan',
91
+ 'Kenya',
92
+ 'Kiribati',
93
+ 'Kuwait',
94
+ 'Kyrgyzstan',
95
+ 'Laos',
96
+ 'Latvia',
97
+ 'Lebanon',
98
+ 'Lesotho',
99
+ 'Liberia',
100
+ 'Libya',
101
+ 'Liechtenstein',
102
+ 'Lithuania',
103
+ 'Luxembourg',
104
+ 'Madagascar',
105
+ 'Malawi',
106
+ 'Malaysia',
107
+ 'Maldives',
108
+ 'Mali',
109
+ 'Malta',
110
+ 'Marshall Islands',
111
+ 'Mauritania',
112
+ 'Mauritius',
113
+ 'Mexico',
114
+ 'Micronesia',
115
+ 'Moldova',
116
+ 'Monaco',
117
+ 'Mongolia',
118
+ 'Montenegro',
119
+ 'Morocco',
120
+ 'Mozambique',
121
+ 'Myanmar (formerly Burma)',
122
+ 'Namibia',
123
+ 'Nauru',
124
+ 'Nepal',
125
+ 'Netherlands',
126
+ 'New Zealand',
127
+ 'Nicaragua',
128
+ 'Niger',
129
+ 'Nigeria',
130
+ 'North Korea',
131
+ 'North Macedonia',
132
+ 'Norway',
133
+ 'Oman',
134
+ 'Pakistan',
135
+ 'Palau',
136
+ 'Palestine State',
137
+ 'Panama',
138
+ 'Papua New Guinea',
139
+ 'Paraguay',
140
+ 'Peru',
141
+ 'Philippines',
142
+ 'Poland',
143
+ 'Portugal',
144
+ 'Qatar',
145
+ 'Romania',
146
+ 'Russia',
147
+ 'Rwanda',
148
+ 'Saint Kitts and Nevis',
149
+ 'Saint Lucia',
150
+ 'Saint Vincent and the Grenadines',
151
+ 'Samoa',
152
+ 'San Marino',
153
+ 'Sao Tome and Principe',
154
+ 'Saudi Arabia',
155
+ 'Senegal',
156
+ 'Serbia',
157
+ 'Seychelles',
158
+ 'Sierra Leone',
159
+ 'Singapore',
160
+ 'Slovakia',
161
+ 'Slovenia',
162
+ 'Solomon Islands',
163
+ 'Somalia',
164
+ 'South Africa',
165
+ 'South Korea',
166
+ 'South Sudan',
167
+ 'Spain',
168
+ 'Sri Lanka',
169
+ 'Sudan',
170
+ 'Suriname',
171
+ 'Sweden',
172
+ 'Switzerland',
173
+ 'Syria',
174
+ 'Tajikistan',
175
+ 'Tanzania',
176
+ 'Thailand',
177
+ 'Timor-Leste',
178
+ 'Togo',
179
+ 'Tonga',
180
+ 'Trinidad and Tobago',
181
+ 'Tunisia',
182
+ 'Turkey',
183
+ 'Turkmenistan',
184
+ 'Tuvalu',
185
+ 'Uganda',
186
+ 'Ukraine',
187
+ 'United Arab Emirates',
188
+ 'United Kingdom',
189
+ 'United States of America',
190
+ 'Uruguay',
191
+ 'Uzbekistan',
192
+ 'Vanuatu',
193
+ 'Venezuela',
194
+ 'Vietnam',
195
+ 'Yemen',
196
+ 'Zambia',
197
+ 'Zimbabwe',
198
+ ];
package/src/Menu/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './Menu';
2
2
  export * from './MenuButton';
3
+ export * from './ContextMenuTrigger';
3
4
  export * from './MenuItem';
4
5
  export * from './MenuList';
5
6
  export * from './MenuPopover';
@@ -18,11 +18,17 @@ export function useControlledState<
18
18
  ): [V, CustomEventHandler<E, H>] {
19
19
  const isControlled = valueProp !== undefined;
20
20
  const wasControlled = useRef(isControlled);
21
+ const hasWarned = useRef(false);
21
22
  const [valueState, setValueState] = useState<V>(defaultValue);
22
23
 
23
24
  if (isControlled) {
24
- if (wasControlled.current && process.env.NODE_ENV !== 'production') {
25
+ if (
26
+ wasControlled.current &&
27
+ process.env.NODE_ENV !== 'production' &&
28
+ !hasWarned.current
29
+ ) {
25
30
  console.warn('Trying to change from controlled to uncontrolled.');
31
+ hasWarned.current = true;
26
32
  }
27
33
  return [
28
34
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion