@basic-ui/core 0.0.29 → 0.0.33

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 (130) hide show
  1. package/build/cjs/index.js +90 -71
  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/MenuList.js +7 -5
  6. package/build/esm/Menu/MenuList.js.map +1 -1
  7. package/build/esm/Tooltip/Tooltip.d.ts +1 -0
  8. package/build/esm/Tooltip/Tooltip.js +10 -3
  9. package/build/esm/Tooltip/Tooltip.js.map +1 -1
  10. package/build/esm/Tooltip/stateMachine.d.ts +17 -19
  11. package/build/esm/Tooltip/stateMachine.js +45 -49
  12. package/build/esm/Tooltip/stateMachine.js.map +1 -1
  13. package/build/esm/Tooltip/useTooltip.js +9 -9
  14. package/build/esm/Tooltip/useTooltip.js.map +1 -1
  15. package/build/tsconfig.tsbuildinfo +384 -89
  16. package/package.json +5 -5
  17. package/src/Accordion/Accordion.story.tsx +72 -0
  18. package/src/Accordion/Accordion.tsx +51 -0
  19. package/src/Accordion/AccordionBody.tsx +53 -0
  20. package/src/Accordion/AccordionHeader.tsx +165 -0
  21. package/src/Accordion/AccordionItem.tsx +43 -0
  22. package/src/Accordion/context.ts +35 -0
  23. package/src/Accordion/index.ts +4 -0
  24. package/src/Accordion/scopeQuery.ts +7 -0
  25. package/src/Accordion/styles.css +21 -0
  26. package/src/CheckBox/CheckBox.tsx +41 -0
  27. package/src/CheckBox/index.ts +1 -0
  28. package/src/ComboBox/ComboBox.story.tsx +118 -0
  29. package/src/ComboBox/Combobox.tsx +153 -0
  30. package/src/ComboBox/ComboboxButton.tsx +60 -0
  31. package/src/ComboBox/ComboboxInput.tsx +178 -0
  32. package/src/ComboBox/ComboboxLabel.tsx +32 -0
  33. package/src/ComboBox/ComboboxList.tsx +47 -0
  34. package/src/ComboBox/ComboboxOption.tsx +107 -0
  35. package/src/ComboBox/ComboboxPopover.tsx +58 -0
  36. package/src/ComboBox/cities.ts +23194 -0
  37. package/src/ComboBox/context.ts +33 -0
  38. package/src/ComboBox/hooks.tsx +428 -0
  39. package/src/ComboBox/index.ts +8 -0
  40. package/src/ComboBox/makeHash.ts +19 -0
  41. package/src/ComboBox/scopeQuery.ts +6 -0
  42. package/src/ComboBox/styles.css +30 -0
  43. package/src/FocusLock/FocusLock.tsx +59 -0
  44. package/src/FocusLock/index.ts +1 -0
  45. package/src/FocusLock/tabUtils.ts +28 -0
  46. package/src/FocusLock/useFocusLock.ts +61 -0
  47. package/src/List/List.story.tsx +17 -0
  48. package/src/List/List.tsx +17 -0
  49. package/src/List/ListItem.tsx +23 -0
  50. package/src/List/context.ts +19 -0
  51. package/src/List/index.ts +2 -0
  52. package/src/Menu/.gitkeep +0 -0
  53. package/src/Menu/Menu.story.tsx +158 -0
  54. package/src/Menu/Menu.tsx +60 -0
  55. package/src/Menu/MenuButton.tsx +83 -0
  56. package/src/Menu/MenuItem.tsx +83 -0
  57. package/src/Menu/MenuList.tsx +201 -0
  58. package/src/Menu/MenuPopover.tsx +25 -0
  59. package/src/Menu/context.ts +32 -0
  60. package/src/Menu/index.ts +5 -0
  61. package/src/Menu/scope.ts +7 -0
  62. package/src/Menu/styles.css +42 -0
  63. package/src/Modal/Modal.story.tsx +242 -0
  64. package/src/Modal/Modal.tsx +42 -0
  65. package/src/Modal/ModalBackdrop.tsx +72 -0
  66. package/src/Modal/NavDrawer.story.tsx +157 -0
  67. package/src/Modal/index.ts +2 -0
  68. package/src/Modal/styles.css +46 -0
  69. package/src/Popover/.gitkeep +0 -0
  70. package/src/Popper/Popper.story.tsx +267 -0
  71. package/src/Popper/Popper.tsx +149 -0
  72. package/src/Popper/PopperArrow.tsx +36 -0
  73. package/src/Popper/context.ts +9 -0
  74. package/src/Popper/index.ts +3 -0
  75. package/src/Popper/styles.css +60 -0
  76. package/src/Portal/Portal.tsx +20 -0
  77. package/src/Portal/index.ts +1 -0
  78. package/src/RadioButton/RadioButton.story.tsx +73 -0
  79. package/src/RadioButton/RadioButton.tsx +48 -0
  80. package/src/RadioButton/RadioGroup.tsx +56 -0
  81. package/src/RadioButton/context.ts +19 -0
  82. package/src/RadioButton/index.ts +2 -0
  83. package/src/SkipNav/SkipNav.tsx +16 -0
  84. package/src/SkipNav/index.tsx +1 -0
  85. package/src/Spinner/Spinner.story.tsx +30 -0
  86. package/src/Spinner/Spinner.tsx +112 -0
  87. package/src/Spinner/SpinnerButton.tsx +48 -0
  88. package/src/Spinner/context.ts +21 -0
  89. package/src/Spinner/index.ts +2 -0
  90. package/src/Spinner/styles.css +23 -0
  91. package/src/Tabs/Tab.story.tsx +78 -0
  92. package/src/Tabs/Tab.tsx +131 -0
  93. package/src/Tabs/TabList.tsx +63 -0
  94. package/src/Tabs/TabPanel.tsx +52 -0
  95. package/src/Tabs/TabPanels.tsx +30 -0
  96. package/src/Tabs/Tabs.tsx +47 -0
  97. package/src/Tabs/context.ts +30 -0
  98. package/src/Tabs/index.tsx +5 -0
  99. package/src/Tabs/scopeQuery.ts +6 -0
  100. package/src/Tabs/styles.css +0 -0
  101. package/src/Tooltip/.gitkeep +0 -0
  102. package/src/Tooltip/Tooltip.story.tsx +59 -0
  103. package/src/Tooltip/Tooltip.tsx +48 -0
  104. package/src/Tooltip/index.ts +1 -0
  105. package/src/Tooltip/stateMachine.ts +196 -0
  106. package/src/Tooltip/styles.css +17 -0
  107. package/src/Tooltip/useTooltip.ts +128 -0
  108. package/src/hooks/index.ts +14 -0
  109. package/src/hooks/useAutoFocus.ts +13 -0
  110. package/src/hooks/useChildrenCounter.ts +50 -0
  111. package/src/hooks/useControlledState.ts +37 -0
  112. package/src/hooks/useFocusReturn.ts +23 -0
  113. package/src/hooks/useFocusState.ts +28 -0
  114. package/src/hooks/useGestureHandlers.ts +217 -0
  115. package/src/hooks/useId.ts +18 -0
  116. package/src/hooks/useMeasure.ts +33 -0
  117. package/src/hooks/useOnClickOutside.ts +32 -0
  118. package/src/hooks/useOnKeyDown.ts +18 -0
  119. package/src/hooks/useReducerMachine.ts +59 -0
  120. package/src/hooks/useRemoveBodyScroll.ts +37 -0
  121. package/src/hooks/useScope.ts +51 -0
  122. package/src/hooks/useThrottle.ts +19 -0
  123. package/src/index.ts +19 -0
  124. package/src/utils/assignRef.ts +27 -0
  125. package/src/utils/clamp.ts +3 -0
  126. package/src/utils/createSubscription.ts +16 -0
  127. package/src/utils/getCircularIndex.ts +7 -0
  128. package/src/utils/index.ts +4 -0
  129. package/src/utils/rubberBandClamp.ts +25 -0
  130. package/src/utils/wrapEvent.ts +20 -0
@@ -0,0 +1,30 @@
1
+ import { forwardRef, Fragment, Children, cloneElement } from 'react';
2
+ import type * as React from 'react';
3
+
4
+ export interface TabPanelsProps {
5
+ as?: React.ElementType<any>;
6
+ innerAs?: React.ElementType<any>;
7
+ lazy?: boolean;
8
+ children?: React.ReactNode;
9
+ }
10
+
11
+ export const TabPanels = forwardRef<HTMLDivElement, TabPanelsProps>(
12
+ function TabPanels(props, forwardedRef) {
13
+ const {
14
+ as: Comp = Fragment,
15
+ children: childrenProps,
16
+ lazy,
17
+ ...otherProps
18
+ } = props;
19
+
20
+ const children = Children.map(childrenProps, (node, index) =>
21
+ cloneElement(node as any, { index, lazy })
22
+ );
23
+
24
+ return (
25
+ <Comp ref={forwardedRef} {...otherProps}>
26
+ {children}
27
+ </Comp>
28
+ );
29
+ }
30
+ );
@@ -0,0 +1,47 @@
1
+ import { forwardRef, Fragment, useState } from 'react';
2
+ import type * as React from 'react';
3
+ import { TabsProvider } from './context';
4
+ import { useControlledState } from '../hooks';
5
+
6
+ export interface TabsProps {
7
+ as?: React.ElementType<any>;
8
+ innerAs?: React.ElementType<any>;
9
+ children?: React.ReactNode;
10
+ onChange?: (
11
+ e:
12
+ | React.MouseEvent<HTMLButtonElement>
13
+ | React.KeyboardEvent<HTMLButtonElement>,
14
+ value: number
15
+ ) => void;
16
+ index?: number;
17
+ defaultIndex?: number;
18
+ }
19
+
20
+ export const Tabs = forwardRef<HTMLDivElement, TabsProps>(function Tabs(
21
+ props,
22
+ forwardedRef
23
+ ) {
24
+ const {
25
+ as: Comp = Fragment,
26
+ index: indexProp,
27
+ onChange: onChangeProp,
28
+ defaultIndex = 0,
29
+ ...otherProps
30
+ } = props;
31
+ const [index, onChange] = useControlledState(
32
+ indexProp,
33
+ onChangeProp,
34
+ defaultIndex,
35
+ (setState) => (e, idx) => setState(idx)
36
+ );
37
+
38
+ const [tabListId, setTabListId] = useState<string | null>(null);
39
+
40
+ return (
41
+ <TabsProvider
42
+ value={{ currentIndex: index || 0, onChange, tabListId, setTabListId }}
43
+ >
44
+ <Comp ref={forwardedRef} {...otherProps} />
45
+ </TabsProvider>
46
+ );
47
+ });
@@ -0,0 +1,30 @@
1
+ import { createContext, useContext } from 'react';
2
+ import { Scope } from '../hooks/useScope';
3
+
4
+ // Tabs Component
5
+ export interface TabsContextProps {
6
+ currentIndex: number;
7
+ onChange?: (
8
+ e:
9
+ | React.MouseEvent<HTMLButtonElement>
10
+ | React.KeyboardEvent<HTMLButtonElement>,
11
+ value: number
12
+ ) => void;
13
+ tabListId: string | null;
14
+ setTabListId: (v: string | null) => void;
15
+ }
16
+
17
+ const tabsContext = createContext<TabsContextProps | null>(null);
18
+ export const { Provider: TabsProvider } = tabsContext;
19
+ export const useTabsContext = () => useContext(tabsContext);
20
+
21
+ // TabList Component
22
+ export interface TabListContextProps {
23
+ tabsScope: Scope<HTMLElement>;
24
+ manualActivation: boolean;
25
+ vertical: boolean;
26
+ }
27
+
28
+ const tablistContext = createContext<TabListContextProps | null>(null);
29
+ export const { Provider: TabListProvider } = tablistContext;
30
+ export const useTabListContext = () => useContext(tablistContext);
@@ -0,0 +1,5 @@
1
+ export * from './Tabs';
2
+ export * from './Tab';
3
+ export * from './TabList';
4
+ export * from './TabPanels';
5
+ export * from './TabPanel';
@@ -0,0 +1,6 @@
1
+ export function scopeQuery(
2
+ nodeType: string,
3
+ props: Record<string, unknown>
4
+ ): boolean {
5
+ return props['data-tab'] === '';
6
+ }
File without changes
File without changes
@@ -0,0 +1,59 @@
1
+ import React, { useRef, forwardRef } from 'react';
2
+ import { Tooltip } from './';
3
+ import { storiesOf } from '@storybook/react';
4
+ import { Popper } from '../Popper/Popper';
5
+ import { InjectedTooltipProps } from './useTooltip';
6
+ import './styles.css';
7
+
8
+ const StyledTooltip = forwardRef<HTMLDivElement, InjectedTooltipProps>(
9
+ ({ children, anchorEl, ...props }, ref) => {
10
+ return (
11
+ <Popper ref={ref} anchorEl={anchorEl} distance={10}>
12
+ <div
13
+ style={{
14
+ backgroundColor: '#fff',
15
+ boxShadow: '0 0 4px 1px rgba(187,187,187,0.7)',
16
+ padding: 8,
17
+ }}
18
+ {...props}
19
+ >
20
+ {children}
21
+ </div>
22
+ </Popper>
23
+ );
24
+ }
25
+ );
26
+
27
+ const Example = () => {
28
+ const buttonRef = useRef();
29
+ return (
30
+ <div style={{ margin: 100, display: 'flex' }}>
31
+ <Tooltip label={'Im groot'} as={StyledTooltip}>
32
+ <button ref={buttonRef}>Hello</button>
33
+ </Tooltip>
34
+ <Tooltip label={'Nice'} as={StyledTooltip}>
35
+ <button ref={buttonRef}>Hello</button>
36
+ </Tooltip>
37
+ </div>
38
+ );
39
+ };
40
+
41
+ const ExampleWithHidingButton = () => {
42
+ const buttonRef = useRef();
43
+ return (
44
+ <ul style={{ margin: 100 }}>
45
+ {Array.from({ length: 20 }).map((_, index) => (
46
+ <li data-hide-child-buttons="" key={index}>
47
+ <Tooltip label={'Im groot'} as={StyledTooltip}>
48
+ <button ref={buttonRef}>Hello</button>
49
+ </Tooltip>
50
+ </li>
51
+ ))}
52
+ </ul>
53
+ );
54
+ };
55
+
56
+ const stories = storiesOf('Components/Tooltip', module);
57
+
58
+ stories.add('controlled', () => <Example />);
59
+ stories.add('with hiding button', () => <ExampleWithHidingButton />);
@@ -0,0 +1,48 @@
1
+ import React, {
2
+ forwardRef,
3
+ cloneElement,
4
+ Children,
5
+ ReactElement,
6
+ RefAttributes,
7
+ } from 'react';
8
+ import { useTooltip } from './useTooltip';
9
+ export type { InjectedTooltipProps } from './useTooltip';
10
+
11
+ export interface TooltipProps extends React.HTMLAttributes<HTMLDivElement> {
12
+ as?: React.ElementType<any>;
13
+ innerAs?: React.ElementType<any>;
14
+ children?: React.ReactNode;
15
+ label: React.ReactNode;
16
+ disabled?: boolean;
17
+ }
18
+
19
+ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
20
+ function Tooltip(props, forwardedRef) {
21
+ const {
22
+ as: Comp = 'div',
23
+ innerAs,
24
+ children,
25
+ disabled = false,
26
+ ...otherProps
27
+ } = props;
28
+ const child: ReactElement & RefAttributes<any> = Children.only(
29
+ children
30
+ ) as any;
31
+ const [childProps, { visible, ...tooltipProps }] = useTooltip(
32
+ child.props,
33
+ child.ref,
34
+ otherProps
35
+ );
36
+
37
+ if (disabled) {
38
+ return <>{child}</>;
39
+ }
40
+
41
+ return (
42
+ <>
43
+ {cloneElement(child, childProps)}
44
+ {visible && <Comp as={innerAs} ref={forwardedRef} {...tooltipProps} />}
45
+ </>
46
+ );
47
+ }
48
+ );
@@ -0,0 +1 @@
1
+ export * from './Tooltip';
@@ -0,0 +1,196 @@
1
+ import { createSubscription } from '../utils/createSubscription';
2
+ import { StateChart as GenericStateChart } from '../hooks/useReducerMachine';
3
+
4
+ ////////////////////////////////////////////////////////////////////////////////
5
+ // Timeouts:
6
+
7
+ // Manages when the user "rests" on an element. Keeps the interface from being
8
+ // flashing tooltips all the time as the user moves the mouse around the screen.
9
+ let restTimeout: number;
10
+ function startRestTimer() {
11
+ window.clearTimeout(restTimeout);
12
+ restTimeout = window.setTimeout(() => {
13
+ send(Rest, undefined);
14
+ }, 200);
15
+ }
16
+
17
+ function clearRestTimer() {
18
+ window.clearTimeout(restTimeout);
19
+ }
20
+
21
+ // Manages the delay to hide the tooltip after rest leaves.
22
+ let leavingVisibleTimer: number;
23
+
24
+ function startLeavingVisibleTimer() {
25
+ window.clearTimeout(leavingVisibleTimer);
26
+ leavingVisibleTimer = window.setTimeout(
27
+ () => send(TimeComplete, undefined),
28
+ 100
29
+ );
30
+ }
31
+
32
+ function clearLeavingVisibleTimer() {
33
+ window.clearTimeout(leavingVisibleTimer);
34
+ }
35
+
36
+ ////////////////////////////////////////////////////////////////////////////////
37
+ // State machine
38
+
39
+ export type TooltipStates =
40
+ | 'IDLE'
41
+ | 'FOCUSED'
42
+ | 'VISIBLE'
43
+ | 'LEAVING_VISIBLE'
44
+ | 'DISMISSED';
45
+
46
+ // Nothing goin' on
47
+ export const Idle = 'IDLE' as const;
48
+ // We're considering showing the tooltip, but we're gonna wait a sec
49
+ export const Focused = 'FOCUSED' as const;
50
+ // It's on!
51
+ export const Visible = 'VISIBLE' as const;
52
+ // Focus has left, but we want to keep it visible for a sec
53
+ export const LeavingVisible = 'LEAVING_VISIBLE' as const;
54
+ // The user clicked the tool, so we want to hide the thing, we can't just use
55
+ // IDLE because we need to ignore mousemove, etc.
56
+ export const Dismissed = 'DISMISSED' as const;
57
+
58
+ export type TooltipEventTypes =
59
+ | 'BLUR'
60
+ | 'FOCUS'
61
+ | 'GLOBAL_MOUSE_MOVE'
62
+ | 'MOUSE_DOWN'
63
+ | 'MOUSE_ENTER'
64
+ | 'MOUSE_LEAVE'
65
+ | 'MOUSE_MOVE'
66
+ | 'REST'
67
+ | 'SELECT_WITH_KEYBOARD'
68
+ | 'TIME_COMPLETE';
69
+
70
+ export const Blur = 'BLUR' as const;
71
+ export const Focus = 'FOCUS' as const;
72
+ export const GlobalMouseMove = 'GLOBAL_MOUSE_MOVE' as const;
73
+ export const MouseDown = 'MOUSE_DOWN' as const;
74
+ export const MouseEnter = 'MOUSE_ENTER' as const;
75
+ export const MouseLeave = 'MOUSE_LEAVE' as const;
76
+ export const MouseMove = 'MOUSE_MOVE' as const;
77
+ export const Rest = 'REST' as const;
78
+ export const SelectWithKeyboard = 'SELECT_WITH_KEYBOARD' as const;
79
+ export const TimeComplete = 'TIME_COMPLETE' as const;
80
+
81
+ export const subscription = createSubscription();
82
+ export const state = {
83
+ current: {
84
+ state: Idle as TooltipStates,
85
+ id: '',
86
+ },
87
+ };
88
+
89
+ function clearContextId() {
90
+ state.current.id = '';
91
+ }
92
+
93
+ const chart: GenericStateChart<TooltipStates, TooltipEventTypes> = {
94
+ initial: Idle,
95
+ states: {
96
+ [Idle]: {
97
+ enter: () => {
98
+ clearContextId();
99
+ },
100
+ on: {
101
+ [MouseEnter]: Focused,
102
+ [Focus]: Visible,
103
+ },
104
+ },
105
+ [Focused]: {
106
+ enter: startRestTimer,
107
+ leave: clearRestTimer,
108
+ on: {
109
+ [MouseMove]: Focused,
110
+ [MouseLeave]: Idle,
111
+ [MouseDown]: Dismissed,
112
+ [Blur]: Idle,
113
+ [Rest]: Visible,
114
+ },
115
+ },
116
+ [Visible]: {
117
+ on: {
118
+ [Focus]: Focused,
119
+ [MouseEnter]: Focused,
120
+ [MouseLeave]: LeavingVisible,
121
+ [Blur]: LeavingVisible,
122
+ [MouseDown]: Dismissed,
123
+ [SelectWithKeyboard]: Dismissed,
124
+ [GlobalMouseMove]: LeavingVisible,
125
+ },
126
+ },
127
+ [LeavingVisible]: {
128
+ enter: () => {
129
+ startLeavingVisibleTimer();
130
+ },
131
+ leave: () => {
132
+ clearLeavingVisibleTimer();
133
+ clearContextId();
134
+ },
135
+ on: {
136
+ [MouseEnter]: Visible,
137
+ [Focus]: Visible,
138
+ [TimeComplete]: Idle,
139
+ },
140
+ },
141
+ [Dismissed]: {
142
+ leave: () => {
143
+ clearContextId();
144
+ },
145
+ on: {
146
+ [MouseLeave]: Idle,
147
+ [Blur]: Idle,
148
+ },
149
+ },
150
+ },
151
+ };
152
+
153
+ function transition(
154
+ currentState: typeof state['current'],
155
+ event: TooltipEventTypes,
156
+ payload?: Omit<typeof state['current'], 'state'>
157
+ ): typeof state['current'] {
158
+ const currentStateValue = currentState.state;
159
+ const stateDef = chart.states[currentState.state];
160
+ const nextState = stateDef?.on?.[event];
161
+
162
+ // Really useful for debugging
163
+ // console.log({
164
+ // event,
165
+ // state: state.current.state,
166
+ // id: state.current.id,
167
+ // nextState,
168
+ // });
169
+
170
+ if (!nextState) {
171
+ return currentState;
172
+ }
173
+
174
+ if (stateDef && stateDef.leave) {
175
+ stateDef.leave(currentStateValue, payload);
176
+ }
177
+
178
+ const nextStateValue = nextState;
179
+ const nextDef = chart.states[nextStateValue];
180
+ if (nextDef && nextDef.enter) {
181
+ nextDef.enter(nextStateValue, payload);
182
+ }
183
+
184
+ return { ...currentState, ...payload, state: nextStateValue };
185
+ }
186
+
187
+ export function send<T extends TooltipEventTypes>(
188
+ event: T,
189
+ payload?: Omit<typeof state['current'], 'state'>
190
+ ) {
191
+ const nextState = transition(state.current, event, payload);
192
+ if (state.current !== nextState) {
193
+ state.current = nextState;
194
+ subscription.notify();
195
+ }
196
+ }
@@ -0,0 +1,17 @@
1
+ [data-hide-child-buttons=""] {
2
+ background-color: #ddd;
3
+ display: flex;
4
+ padding: 20px;
5
+ min-height: 62px;
6
+ box-sizing: border-box;
7
+ border-bottom: 1px solid #aaa;
8
+ justify-content: flex-end;
9
+ }
10
+
11
+ [data-hide-child-buttons=""] button {
12
+ display: none;
13
+ }
14
+
15
+ [data-hide-child-buttons=""]:hover button {
16
+ display: block;
17
+ }
@@ -0,0 +1,128 @@
1
+ import React, { useRef, RefAttributes, useEffect, useState } from 'react';
2
+ import { assignMultipleRefs } from '../utils/assignRef';
3
+ import { wrapEvent } from '../utils/wrapEvent';
4
+ import { useId } from '../hooks/useId';
5
+ import {
6
+ send,
7
+ state,
8
+ subscription,
9
+ Blur,
10
+ Focus,
11
+ LeavingVisible,
12
+ MouseDown,
13
+ MouseEnter,
14
+ MouseLeave,
15
+ MouseMove,
16
+ SelectWithKeyboard,
17
+ Visible,
18
+ } from './stateMachine';
19
+
20
+ export type ChildProps = React.HTMLAttributes<HTMLElement> &
21
+ RefAttributes<HTMLElement>;
22
+
23
+ export interface InjectedTooltipProps
24
+ extends React.HTMLAttributes<HTMLElement> {
25
+ anchorEl: React.RefObject<HTMLElement>;
26
+ visible: boolean;
27
+ children?: React.ReactNode;
28
+ }
29
+
30
+ export function useTooltip(
31
+ childProps: ChildProps,
32
+ childRef: React.Ref<HTMLElement> | undefined,
33
+ tooltipProps: React.HTMLAttributes<HTMLElement> & { label?: React.ReactNode }
34
+ ): [ChildProps, InjectedTooltipProps] {
35
+ const {
36
+ onMouseEnter,
37
+ onMouseLeave,
38
+ onMouseMove,
39
+ onMouseDown,
40
+ onKeyDown,
41
+ onFocus,
42
+ onBlur,
43
+ } = childProps;
44
+ const anchorEl = useRef<HTMLElement>(null);
45
+ const [visible, setVisible] = useState(false);
46
+ const id = useId('');
47
+
48
+ useEffect(() => {
49
+ subscription.subscribe(() => {
50
+ setVisible(
51
+ (state.current.state === Visible ||
52
+ state.current.state === LeavingVisible) &&
53
+ state.current.id === id
54
+ );
55
+ });
56
+ }, [id]);
57
+
58
+ function handleMouseEnter() {
59
+ send(MouseEnter, { id });
60
+ }
61
+
62
+ function handleMouseMove() {
63
+ send(MouseMove, { id });
64
+ }
65
+
66
+ function handleMouseLeave() {
67
+ send(MouseLeave);
68
+ }
69
+
70
+ function handleMouseDown() {
71
+ // Allow quick click from one tool to another
72
+ if (state.current.id === id) {
73
+ send(MouseDown);
74
+ }
75
+ }
76
+
77
+ function handleFocus() {
78
+ send(Focus, { id });
79
+ }
80
+
81
+ function handleBlur() {
82
+ // Allow quick click from one tool to another
83
+ if (state.current.id === id) {
84
+ send(Blur, undefined);
85
+ }
86
+ }
87
+
88
+ function handleKeyDown(event: React.KeyboardEvent<HTMLElement>) {
89
+ if (event.key === 'Enter' || event.key === ' ') {
90
+ send(SelectWithKeyboard);
91
+ }
92
+ }
93
+
94
+ const {
95
+ label: children,
96
+ onMouseEnter: tooltipOnMouseEnter,
97
+ onMouseLeave: tooltipOnMouseLeave,
98
+ onMouseMove: tooltipOnMouseMove,
99
+ ...otherTooltipProps
100
+ } = tooltipProps;
101
+
102
+ const tooltipId = `tooltip-${id}`;
103
+ return [
104
+ {
105
+ ...childProps,
106
+ ref: assignMultipleRefs(childRef, anchorEl),
107
+ ...(visible && { 'aria-describedby': tooltipId }),
108
+ onMouseEnter: wrapEvent(onMouseEnter, handleMouseEnter),
109
+ onMouseLeave: wrapEvent(onMouseLeave, handleMouseLeave),
110
+ onMouseMove: wrapEvent(onMouseMove, handleMouseMove),
111
+ onMouseDown: wrapEvent(onMouseDown, handleMouseDown),
112
+ onFocus: wrapEvent(onFocus, handleFocus),
113
+ onBlur: wrapEvent(onBlur, handleBlur),
114
+ onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
115
+ },
116
+ {
117
+ id: tooltipId,
118
+ anchorEl,
119
+ visible,
120
+ children,
121
+ onMouseEnter: wrapEvent(tooltipOnMouseEnter, handleMouseEnter),
122
+ onMouseLeave: wrapEvent(tooltipOnMouseLeave, handleMouseLeave),
123
+ onMouseMove: wrapEvent(tooltipOnMouseMove, handleMouseMove),
124
+ role: 'tooltip',
125
+ ...otherTooltipProps,
126
+ },
127
+ ];
128
+ }
@@ -0,0 +1,14 @@
1
+ export * from './useAutoFocus';
2
+ export * from './useControlledState';
3
+ export * from './useChildrenCounter';
4
+ export * from './useFocusReturn';
5
+ export * from './useFocusState';
6
+ export * from './useId';
7
+ export * from './useOnClickOutside';
8
+ export * from './useOnKeyDown';
9
+ export * from './useReducerMachine';
10
+ export * from './useRemoveBodyScroll';
11
+ export * from './useThrottle';
12
+ export * from './useMeasure';
13
+ export * from './useGestureHandlers';
14
+ export * from './useScope';
@@ -0,0 +1,13 @@
1
+ import { useEffect } from 'react';
2
+
3
+ export function useAutoFocus(
4
+ open: boolean,
5
+ elementRef: React.MutableRefObject<HTMLElement | null>
6
+ ) {
7
+ useEffect(() => {
8
+ if (open) {
9
+ elementRef.current && elementRef.current.focus();
10
+ }
11
+ // eslint-disable-next-line react-hooks/exhaustive-deps
12
+ }, [open]);
13
+ }
@@ -0,0 +1,50 @@
1
+ import { MutableRefObject, useEffect } from 'react';
2
+
3
+ export function useChildrenCounterParent<T>(
4
+ itemsRef: MutableRefObject<T[] & { isNewRender?: boolean }>
5
+ ) {
6
+ // Reset the options ref every render so that they are always
7
+ // accurate and ready for keyboard navigation handlers. Using layout
8
+ // effect to schedule this effect before the ComboboxOptions push into
9
+ // the array
10
+ itemsRef.current = [];
11
+ itemsRef.current.isNewRender = true;
12
+
13
+ useEffect(() => {
14
+ // Rendering is finished. Meaning any children can now rerender,
15
+ // and they should not push any new items to our array, because
16
+ // it is not a new render
17
+ itemsRef.current.isNewRender = false;
18
+ });
19
+
20
+ useEffect(() => {
21
+ // When we are unmounting, it means there are no children anymore.
22
+ // Clear out our items array
23
+ return () => {
24
+ itemsRef.current = [];
25
+ };
26
+ }, [itemsRef]);
27
+ }
28
+
29
+ export function useChildrenCounterChild<T>(
30
+ itemsRef: MutableRefObject<T[] & { isNewRender?: boolean }> | undefined,
31
+ itemIndexRef: MutableRefObject<number>,
32
+ obj: T | ((idx: number) => T),
33
+ disabled = false
34
+ ) {
35
+ if (itemsRef && itemsRef.current.isNewRender) {
36
+ if (disabled) {
37
+ itemIndexRef.current = -1;
38
+ return;
39
+ }
40
+
41
+ // push this option to the optionsRef array
42
+ itemIndexRef.current = itemsRef.current.length;
43
+
44
+ if (obj instanceof Function) {
45
+ itemsRef.current.push(obj(itemIndexRef.current));
46
+ } else {
47
+ itemsRef.current.push(obj);
48
+ }
49
+ }
50
+ }