@basic-ui/core 0.0.28 → 0.0.32

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 (132) hide show
  1. package/build/cjs/index.js +44 -21
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/FocusLock/useFocusLock.js +21 -7
  4. package/build/esm/FocusLock/useFocusLock.js.map +1 -1
  5. package/build/esm/Menu/Menu.js +0 -3
  6. package/build/esm/Menu/Menu.js.map +1 -1
  7. package/build/esm/Menu/MenuButton.js +7 -5
  8. package/build/esm/Menu/MenuButton.js.map +1 -1
  9. package/build/esm/Menu/MenuList.js +8 -5
  10. package/build/esm/Menu/MenuList.js.map +1 -1
  11. package/build/esm/Menu/context.d.ts +0 -1
  12. package/build/esm/Menu/context.js.map +1 -1
  13. package/build/esm/Tooltip/Tooltip.d.ts +1 -0
  14. package/build/esm/Tooltip/Tooltip.js +10 -3
  15. package/build/esm/Tooltip/Tooltip.js.map +1 -1
  16. package/build/esm/hooks/useId.d.ts +1 -0
  17. package/build/esm/hooks/useId.js.map +1 -1
  18. package/build/tsconfig.tsbuildinfo +11 -11
  19. package/package.json +4 -3
  20. package/src/Accordion/Accordion.story.tsx +72 -0
  21. package/src/Accordion/Accordion.tsx +51 -0
  22. package/src/Accordion/AccordionBody.tsx +53 -0
  23. package/src/Accordion/AccordionHeader.tsx +165 -0
  24. package/src/Accordion/AccordionItem.tsx +43 -0
  25. package/src/Accordion/context.ts +35 -0
  26. package/src/Accordion/index.ts +4 -0
  27. package/src/Accordion/scopeQuery.ts +7 -0
  28. package/src/Accordion/styles.css +21 -0
  29. package/src/CheckBox/CheckBox.tsx +41 -0
  30. package/src/CheckBox/index.ts +1 -0
  31. package/src/ComboBox/ComboBox.story.tsx +118 -0
  32. package/src/ComboBox/Combobox.tsx +153 -0
  33. package/src/ComboBox/ComboboxButton.tsx +60 -0
  34. package/src/ComboBox/ComboboxInput.tsx +178 -0
  35. package/src/ComboBox/ComboboxLabel.tsx +32 -0
  36. package/src/ComboBox/ComboboxList.tsx +47 -0
  37. package/src/ComboBox/ComboboxOption.tsx +107 -0
  38. package/src/ComboBox/ComboboxPopover.tsx +58 -0
  39. package/src/ComboBox/cities.ts +23194 -0
  40. package/src/ComboBox/context.ts +33 -0
  41. package/src/ComboBox/hooks.tsx +428 -0
  42. package/src/ComboBox/index.ts +8 -0
  43. package/src/ComboBox/makeHash.ts +19 -0
  44. package/src/ComboBox/scopeQuery.ts +6 -0
  45. package/src/ComboBox/styles.css +30 -0
  46. package/src/FocusLock/FocusLock.tsx +59 -0
  47. package/src/FocusLock/index.ts +1 -0
  48. package/src/FocusLock/tabUtils.ts +28 -0
  49. package/src/FocusLock/useFocusLock.ts +61 -0
  50. package/src/List/List.story.tsx +17 -0
  51. package/src/List/List.tsx +17 -0
  52. package/src/List/ListItem.tsx +23 -0
  53. package/src/List/context.ts +19 -0
  54. package/src/List/index.ts +2 -0
  55. package/src/Menu/.gitkeep +0 -0
  56. package/src/Menu/Menu.story.tsx +158 -0
  57. package/src/Menu/Menu.tsx +60 -0
  58. package/src/Menu/MenuButton.tsx +83 -0
  59. package/src/Menu/MenuItem.tsx +83 -0
  60. package/src/Menu/MenuList.tsx +201 -0
  61. package/src/Menu/MenuPopover.tsx +25 -0
  62. package/src/Menu/context.ts +32 -0
  63. package/src/Menu/index.ts +5 -0
  64. package/src/Menu/scope.ts +7 -0
  65. package/src/Menu/styles.css +42 -0
  66. package/src/Modal/Modal.story.tsx +242 -0
  67. package/src/Modal/Modal.tsx +42 -0
  68. package/src/Modal/ModalBackdrop.tsx +72 -0
  69. package/src/Modal/NavDrawer.story.tsx +157 -0
  70. package/src/Modal/index.ts +2 -0
  71. package/src/Modal/styles.css +46 -0
  72. package/src/Popover/.gitkeep +0 -0
  73. package/src/Popper/Popper.story.tsx +267 -0
  74. package/src/Popper/Popper.tsx +149 -0
  75. package/src/Popper/PopperArrow.tsx +36 -0
  76. package/src/Popper/context.ts +9 -0
  77. package/src/Popper/index.ts +3 -0
  78. package/src/Popper/styles.css +60 -0
  79. package/src/Portal/Portal.tsx +20 -0
  80. package/src/Portal/index.ts +1 -0
  81. package/src/RadioButton/RadioButton.story.tsx +73 -0
  82. package/src/RadioButton/RadioButton.tsx +48 -0
  83. package/src/RadioButton/RadioGroup.tsx +56 -0
  84. package/src/RadioButton/context.ts +19 -0
  85. package/src/RadioButton/index.ts +2 -0
  86. package/src/SkipNav/SkipNav.tsx +16 -0
  87. package/src/SkipNav/index.tsx +1 -0
  88. package/src/Spinner/Spinner.story.tsx +30 -0
  89. package/src/Spinner/Spinner.tsx +112 -0
  90. package/src/Spinner/SpinnerButton.tsx +48 -0
  91. package/src/Spinner/context.ts +21 -0
  92. package/src/Spinner/index.ts +2 -0
  93. package/src/Spinner/styles.css +23 -0
  94. package/src/Tabs/Tab.story.tsx +78 -0
  95. package/src/Tabs/Tab.tsx +131 -0
  96. package/src/Tabs/TabList.tsx +63 -0
  97. package/src/Tabs/TabPanel.tsx +52 -0
  98. package/src/Tabs/TabPanels.tsx +30 -0
  99. package/src/Tabs/Tabs.tsx +47 -0
  100. package/src/Tabs/context.ts +30 -0
  101. package/src/Tabs/index.tsx +5 -0
  102. package/src/Tabs/scopeQuery.ts +6 -0
  103. package/src/Tabs/styles.css +0 -0
  104. package/src/Tooltip/.gitkeep +0 -0
  105. package/src/Tooltip/Tooltip.story.tsx +43 -0
  106. package/src/Tooltip/Tooltip.tsx +48 -0
  107. package/src/Tooltip/index.ts +1 -0
  108. package/src/Tooltip/stateMachine.ts +185 -0
  109. package/src/Tooltip/useTooltip.ts +121 -0
  110. package/src/hooks/index.ts +14 -0
  111. package/src/hooks/useAutoFocus.ts +13 -0
  112. package/src/hooks/useChildrenCounter.ts +50 -0
  113. package/src/hooks/useControlledState.ts +37 -0
  114. package/src/hooks/useFocusReturn.ts +23 -0
  115. package/src/hooks/useFocusState.ts +28 -0
  116. package/src/hooks/useGestureHandlers.ts +217 -0
  117. package/src/hooks/useId.ts +18 -0
  118. package/src/hooks/useMeasure.ts +33 -0
  119. package/src/hooks/useOnClickOutside.ts +32 -0
  120. package/src/hooks/useOnKeyDown.ts +18 -0
  121. package/src/hooks/useReducerMachine.ts +59 -0
  122. package/src/hooks/useRemoveBodyScroll.ts +37 -0
  123. package/src/hooks/useScope.ts +51 -0
  124. package/src/hooks/useThrottle.ts +19 -0
  125. package/src/index.ts +19 -0
  126. package/src/utils/assignRef.ts +27 -0
  127. package/src/utils/clamp.ts +3 -0
  128. package/src/utils/createSubscription.ts +16 -0
  129. package/src/utils/getCircularIndex.ts +7 -0
  130. package/src/utils/index.ts +4 -0
  131. package/src/utils/rubberBandClamp.ts +25 -0
  132. package/src/utils/wrapEvent.ts +20 -0
@@ -0,0 +1,178 @@
1
+ import { forwardRef, useEffect, useRef } from 'react';
2
+ import type * as React from 'react';
3
+ import {
4
+ useBlur,
5
+ CLEAR,
6
+ CHANGE,
7
+ useKeyDown,
8
+ SELECT_WITH_CLICK,
9
+ FOCUS,
10
+ NAVIGATING,
11
+ INIT,
12
+ useFocusManagement,
13
+ } from './hooks';
14
+ import { assignMultipleRefs, wrapEvent } from '../utils';
15
+ import { useComboBoxContext } from './context';
16
+
17
+ export interface ComboboxInputProps
18
+ extends React.InputHTMLAttributes<HTMLInputElement> {
19
+ // clear on ESC
20
+ clearOnEscape?: boolean;
21
+ // highlights all the text in the box on click when true
22
+ selectOnClick?: boolean;
23
+ // updates the value in the input when navigating w/ the keyboard
24
+ autocomplete?: boolean;
25
+ // initial value for uncontrolled mode
26
+ defaultValue?: string;
27
+ // value for controlled mode
28
+ value?: string;
29
+ onClick?: React.MouseEventHandler<HTMLInputElement>;
30
+ onChange?: React.ChangeEventHandler<HTMLInputElement>;
31
+ onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
32
+ onBlur?: React.FocusEventHandler<HTMLInputElement>;
33
+ onFocus?: React.FocusEventHandler<HTMLInputElement>;
34
+ as?: React.ElementType<any>;
35
+ innerAs?: React.ElementType<any>;
36
+ id?: string;
37
+ }
38
+
39
+ export const ComboboxInput = forwardRef<HTMLInputElement, ComboboxInputProps>(
40
+ function ComboboxInput(
41
+ {
42
+ as: Comp = 'input',
43
+ innerAs,
44
+ selectOnClick = false,
45
+ autocomplete = true,
46
+ clearOnEscape = false,
47
+
48
+ // wrapped events
49
+ onClick,
50
+ onChange,
51
+ onKeyDown,
52
+ onBlur,
53
+ onFocus,
54
+
55
+ id: preferredId,
56
+
57
+ defaultValue = '',
58
+
59
+ // might be controlled
60
+ value: controlledValue,
61
+ ...props
62
+ },
63
+ forwardedRef
64
+ ) {
65
+ const {
66
+ data: { navigationItem, text, lastActionType },
67
+ inputRef,
68
+ state,
69
+ transition,
70
+ listboxIdRef,
71
+ autocompletePropRef,
72
+ clearOnEscapeRef,
73
+ openOnFocus,
74
+ optionsRef,
75
+ labelIdRef,
76
+ } = useComboBoxContext();
77
+
78
+ // Keep focus on the input component
79
+ useFocusManagement(lastActionType, inputRef);
80
+
81
+ // Keep using the defaultValue until the user started interacting
82
+ const hasStartedInteracting = useRef(false);
83
+
84
+ // Because we close the List on blur, we need to track if the blur is
85
+ // caused by clicking inside the list, and if so, don't close the List.
86
+ const selectOnClickRef = useRef(false);
87
+
88
+ const handleBlur = useBlur();
89
+
90
+ // Update ref props
91
+ autocompletePropRef.current = autocomplete;
92
+ clearOnEscapeRef.current = clearOnEscape;
93
+ listboxIdRef.current = preferredId || listboxIdRef.current;
94
+
95
+ // [*]... and when controlled, we don't trigger handleValueChange as the user
96
+ // types, instead the developer controls it with the normal input onChange
97
+ // prop
98
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
99
+ // After the user started typing, lets forget the defaultValue
100
+ hasStartedInteracting.current = true;
101
+
102
+ const text = e.target.value;
103
+ if (text.trim() === '') {
104
+ transition(CLEAR);
105
+ } else {
106
+ transition(CHANGE, { text });
107
+ }
108
+ };
109
+
110
+ const handleKeyDown = useKeyDown();
111
+
112
+ const handleFocus = () => {
113
+ if (selectOnClick) {
114
+ selectOnClickRef.current = true;
115
+ }
116
+ // If we select an option with click, useFocusManagement will focus the
117
+ // input, in those cases we don't want to cause the menu to open back up,
118
+ // so we guard behind these states
119
+ if (openOnFocus && lastActionType !== SELECT_WITH_CLICK) {
120
+ transition(FOCUS, {
121
+ item: navigationItem,
122
+ });
123
+ }
124
+ };
125
+
126
+ const handleClick = () => {
127
+ if (selectOnClickRef.current) {
128
+ selectOnClickRef.current = false;
129
+ inputRef.current && inputRef.current.select();
130
+ }
131
+ };
132
+
133
+ const navigationText =
134
+ navigationItem !== ''
135
+ ? optionsRef.current[navigationItem].text
136
+ : undefined;
137
+
138
+ const fallbackValue = hasStartedInteracting.current ? '' : defaultValue;
139
+
140
+ const inputStrings =
141
+ // When idle, we don't have a navigationText on ArrowUp/Down
142
+ autocomplete && state === NAVIGATING
143
+ ? [navigationText, controlledValue, text, fallbackValue]
144
+ : [controlledValue, text, fallbackValue];
145
+
146
+ const inputValue = inputStrings.find((str) => str !== undefined);
147
+
148
+ // If they are controlling the value we still need to do our transitions, so
149
+ // we have this derived state to emulate onChange of the input as we receive
150
+ // new `value`s ...[*]
151
+ useEffect(() => {
152
+ transition(INIT, { text: inputValue, item: '' });
153
+ // eslint-disable-next-line react-hooks/exhaustive-deps
154
+ }, []);
155
+
156
+ return (
157
+ <Comp
158
+ {...props}
159
+ as={innerAs}
160
+ data-reach-combobox-input=""
161
+ ref={assignMultipleRefs(inputRef, forwardedRef)}
162
+ value={inputValue}
163
+ onClick={wrapEvent(onClick, handleClick)}
164
+ onBlur={wrapEvent(onBlur, handleBlur)}
165
+ onFocus={wrapEvent(onFocus, handleFocus)}
166
+ onChange={wrapEvent(onChange, handleChange)}
167
+ onKeyDown={wrapEvent(onKeyDown, handleKeyDown)}
168
+ aria-labelledby={labelIdRef.current}
169
+ id={listboxIdRef.current}
170
+ aria-autocomplete="both"
171
+ aria-activedescendant={
172
+ navigationItem !== '' ? navigationItem : undefined
173
+ }
174
+ autoComplete="off"
175
+ />
176
+ );
177
+ }
178
+ );
@@ -0,0 +1,32 @@
1
+ import { forwardRef } from 'react';
2
+ import type * as React from 'react';
3
+ import { useComboBoxContext } from './context';
4
+
5
+ export interface ComboboxLabelProps
6
+ extends React.LabelHTMLAttributes<HTMLLabelElement> {
7
+ as?: React.ElementType<any>;
8
+ innerAs?: React.ElementType<any>;
9
+ id?: string;
10
+ }
11
+
12
+ export const ComboboxLabel = forwardRef<HTMLLabelElement, ComboboxLabelProps>(
13
+ function ComboboxButton(
14
+ { as: Comp = 'label', innerAs, id: preferredId, ...props },
15
+ ref
16
+ ) {
17
+ const { listboxIdRef, labelIdRef } = useComboBoxContext();
18
+
19
+ labelIdRef.current = preferredId || labelIdRef.current;
20
+
21
+ return (
22
+ <Comp
23
+ as={innerAs}
24
+ data-reach-combobox-label=""
25
+ htmlFor={listboxIdRef.current}
26
+ id={labelIdRef.current}
27
+ ref={ref}
28
+ {...props}
29
+ />
30
+ );
31
+ }
32
+ );
@@ -0,0 +1,47 @@
1
+ import { forwardRef, useRef, useEffect } from 'react';
2
+ import type * as React from 'react';
3
+ import { useComboBoxContext } from './context';
4
+ import { getScope } from '../hooks';
5
+ import { assignMultipleRefs } from '../utils';
6
+
7
+ export interface ComboboxListProps
8
+ extends React.HTMLAttributes<HTMLUListElement> {
9
+ as?: React.ElementType<any>;
10
+ innerAs?: React.ElementType<any>;
11
+ persistSelection?: boolean;
12
+ children?: React.ReactNode;
13
+ }
14
+
15
+ export const ComboboxList = forwardRef<HTMLUListElement, ComboboxListProps>(
16
+ function ComboboxList(
17
+ {
18
+ // when true, and the list opens again, the option with a matching value will be
19
+ // automatically highleted.
20
+ persistSelection = false,
21
+ as: Comp = 'ul',
22
+ innerAs,
23
+ ...props
24
+ },
25
+ ref
26
+ ) {
27
+ const { persistSelectionRef, labelIdRef, listScope } = useComboBoxContext();
28
+
29
+ const listRef = useRef<HTMLUListElement>();
30
+ useEffect(() => {
31
+ listScope.current = getScope(listRef);
32
+ }, [listScope]);
33
+
34
+ persistSelectionRef.current = persistSelection;
35
+
36
+ return (
37
+ <Comp
38
+ {...props}
39
+ as={innerAs}
40
+ ref={assignMultipleRefs(ref, listRef)}
41
+ data-reach-combobox-list=""
42
+ role="listbox"
43
+ aria-labelledby={labelIdRef.current}
44
+ />
45
+ );
46
+ }
47
+ );
@@ -0,0 +1,107 @@
1
+ /* eslint-disable @typescript-eslint/no-use-before-define */
2
+ import { forwardRef, useEffect, useRef } from 'react';
3
+
4
+ import type * as React from 'react';
5
+ import { SELECT_WITH_CLICK, CLEAR_SELECTION } from './hooks';
6
+ import { wrapEvent } from '../utils/wrapEvent';
7
+ import { useComboBoxContext } from './context';
8
+ import { makeHash } from './makeHash';
9
+
10
+ export interface ComboboxOptionProps
11
+ extends React.LiHTMLAttributes<HTMLLIElement> {
12
+ as?: React.ElementType<any>;
13
+ innerAs?: React.ElementType<any>;
14
+ id: string;
15
+ value: any;
16
+ text?: string;
17
+ onClick?: React.MouseEventHandler<HTMLLIElement>;
18
+ children?: React.ReactNode;
19
+ }
20
+
21
+ export const ComboboxOption = forwardRef<HTMLLIElement, ComboboxOptionProps>(
22
+ function ComboboxOption(
23
+ {
24
+ children,
25
+ id: idProp,
26
+ value: valueProp,
27
+ text: textProp,
28
+ onClick,
29
+ as: Comp = 'li',
30
+ innerAs,
31
+ ...props
32
+ },
33
+ ref
34
+ ) {
35
+ const {
36
+ onSelect,
37
+ data: { navigationItem },
38
+ transition,
39
+ optionsRef,
40
+ } = useComboBoxContext();
41
+ const transitionCleanupRef = useRef(transition);
42
+ const isActiveRef = useRef(false);
43
+
44
+ const value: any = valueProp;
45
+ let text: string = textProp ? textProp : '';
46
+
47
+ if (text.length === 0) {
48
+ if (typeof valueProp === 'string' && valueProp.length > 0) {
49
+ text = valueProp;
50
+ } else {
51
+ throw new Error('Missing text for <ComboboxOption />');
52
+ }
53
+ }
54
+
55
+ const id = String(makeHash(idProp));
56
+
57
+ useEffect(() => {
58
+ const opts = optionsRef.current;
59
+ opts[id] = { value, text };
60
+
61
+ return () => {
62
+ delete opts[id];
63
+ };
64
+ }, [optionsRef, id, text, value]);
65
+
66
+ // Keep updating this ref with the current
67
+ // function pointer for transition, so it can
68
+ // be used by the unmount effect below.
69
+ transitionCleanupRef.current = transition;
70
+ isActiveRef.current = navigationItem === id;
71
+
72
+ useEffect(() => {
73
+ return () => {
74
+ if (isActiveRef.current === true) {
75
+ // clean up selections if this option is getting
76
+ // unmounted and it was the currently selected item
77
+ transitionCleanupRef.current(CLEAR_SELECTION);
78
+ }
79
+ };
80
+ }, []);
81
+
82
+ const handleClick = () => {
83
+ onSelect && onSelect(text, id, value);
84
+ transition(SELECT_WITH_CLICK, { text, item: id });
85
+ };
86
+
87
+ return (
88
+ <Comp
89
+ {...props}
90
+ as={innerAs}
91
+ data-reach-combobox-option=""
92
+ ref={ref}
93
+ id={id}
94
+ role="option"
95
+ aria-selected={isActiveRef.current}
96
+ data-highlighted={isActiveRef.current ? '' : undefined}
97
+ // without this the menu will close from `onBlur`, but with it the
98
+ // element can be `document.activeElement` and then our focus checks in
99
+ // onBlur will work as intended
100
+ tabIndex="-1"
101
+ onClick={wrapEvent(onClick, handleClick)}
102
+ >
103
+ {children || text}
104
+ </Comp>
105
+ );
106
+ }
107
+ );
@@ -0,0 +1,58 @@
1
+ import { forwardRef } from 'react';
2
+ import type * as React from 'react';
3
+ import { useKeyDown, useBlur } from './hooks';
4
+ import { wrapEvent } from '../utils/wrapEvent';
5
+ import { assignMultipleRefs } from '../utils/assignRef';
6
+ import { useComboBoxContext } from './context';
7
+
8
+ export interface ComboboxPopoverProps
9
+ extends React.HTMLAttributes<HTMLDivElement> {
10
+ as?: React.ElementType<any>;
11
+ innerAs?: React.ElementType<any>;
12
+ onBlur?: React.FocusEventHandler<HTMLDivElement>;
13
+ onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
14
+ children?: React.ReactNode;
15
+ }
16
+
17
+ export const ComboboxPopover = forwardRef<HTMLDivElement, ComboboxPopoverProps>(
18
+ function ComboboxPopover(
19
+ {
20
+ // wrapped events
21
+ onKeyDown,
22
+ onBlur,
23
+
24
+ as: Comp = 'div',
25
+ innerAs,
26
+ ...props
27
+ },
28
+ forwardedRef
29
+ ) {
30
+ const { popoverRef, isVisible } = useComboBoxContext();
31
+ const handleKeyDown = useKeyDown();
32
+ const handleBlur = useBlur();
33
+
34
+ // Instead of conditionally rendering the popover we use the `hidden` prop
35
+ // because we don't want to unmount on close (from escape or onSelect). If
36
+ // we unmounted, then we'd lose the optionsRef and the user wouldn't be able
37
+ // to use the arrow keys to pop the list back open. However, the developer
38
+ // can conditionally render the ComboboxPopover if they do want to cause
39
+ // mount/unmount based on the app's own data (like results.length or
40
+ // whatever).
41
+ const hidden = !isVisible;
42
+
43
+ return (
44
+ <Comp
45
+ {...props}
46
+ as={innerAs}
47
+ data-reach-combobox-popover=""
48
+ ref={assignMultipleRefs(popoverRef, forwardedRef)}
49
+ onKeyDown={wrapEvent(onKeyDown, handleKeyDown)}
50
+ onBlur={wrapEvent(onBlur, handleBlur)}
51
+ hidden={hidden}
52
+ // Allow the user to click empty space inside the popover without causing
53
+ // to close from useBlur
54
+ tabIndex="-1"
55
+ />
56
+ );
57
+ }
58
+ );