@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.
- package/build/cjs/index.js +44 -21
- package/build/cjs/index.js.map +1 -1
- package/build/esm/FocusLock/useFocusLock.js +21 -7
- package/build/esm/FocusLock/useFocusLock.js.map +1 -1
- package/build/esm/Menu/Menu.js +0 -3
- package/build/esm/Menu/Menu.js.map +1 -1
- package/build/esm/Menu/MenuButton.js +7 -5
- package/build/esm/Menu/MenuButton.js.map +1 -1
- package/build/esm/Menu/MenuList.js +8 -5
- package/build/esm/Menu/MenuList.js.map +1 -1
- package/build/esm/Menu/context.d.ts +0 -1
- package/build/esm/Menu/context.js.map +1 -1
- package/build/esm/Tooltip/Tooltip.d.ts +1 -0
- package/build/esm/Tooltip/Tooltip.js +10 -3
- package/build/esm/Tooltip/Tooltip.js.map +1 -1
- package/build/esm/hooks/useId.d.ts +1 -0
- package/build/esm/hooks/useId.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +11 -11
- package/package.json +4 -3
- package/src/Accordion/Accordion.story.tsx +72 -0
- package/src/Accordion/Accordion.tsx +51 -0
- package/src/Accordion/AccordionBody.tsx +53 -0
- package/src/Accordion/AccordionHeader.tsx +165 -0
- package/src/Accordion/AccordionItem.tsx +43 -0
- package/src/Accordion/context.ts +35 -0
- package/src/Accordion/index.ts +4 -0
- package/src/Accordion/scopeQuery.ts +7 -0
- package/src/Accordion/styles.css +21 -0
- package/src/CheckBox/CheckBox.tsx +41 -0
- package/src/CheckBox/index.ts +1 -0
- package/src/ComboBox/ComboBox.story.tsx +118 -0
- package/src/ComboBox/Combobox.tsx +153 -0
- package/src/ComboBox/ComboboxButton.tsx +60 -0
- package/src/ComboBox/ComboboxInput.tsx +178 -0
- package/src/ComboBox/ComboboxLabel.tsx +32 -0
- package/src/ComboBox/ComboboxList.tsx +47 -0
- package/src/ComboBox/ComboboxOption.tsx +107 -0
- package/src/ComboBox/ComboboxPopover.tsx +58 -0
- package/src/ComboBox/cities.ts +23194 -0
- package/src/ComboBox/context.ts +33 -0
- package/src/ComboBox/hooks.tsx +428 -0
- package/src/ComboBox/index.ts +8 -0
- package/src/ComboBox/makeHash.ts +19 -0
- package/src/ComboBox/scopeQuery.ts +6 -0
- package/src/ComboBox/styles.css +30 -0
- package/src/FocusLock/FocusLock.tsx +59 -0
- package/src/FocusLock/index.ts +1 -0
- package/src/FocusLock/tabUtils.ts +28 -0
- package/src/FocusLock/useFocusLock.ts +61 -0
- package/src/List/List.story.tsx +17 -0
- package/src/List/List.tsx +17 -0
- package/src/List/ListItem.tsx +23 -0
- package/src/List/context.ts +19 -0
- package/src/List/index.ts +2 -0
- package/src/Menu/.gitkeep +0 -0
- package/src/Menu/Menu.story.tsx +158 -0
- package/src/Menu/Menu.tsx +60 -0
- package/src/Menu/MenuButton.tsx +83 -0
- package/src/Menu/MenuItem.tsx +83 -0
- package/src/Menu/MenuList.tsx +201 -0
- package/src/Menu/MenuPopover.tsx +25 -0
- package/src/Menu/context.ts +32 -0
- package/src/Menu/index.ts +5 -0
- package/src/Menu/scope.ts +7 -0
- package/src/Menu/styles.css +42 -0
- package/src/Modal/Modal.story.tsx +242 -0
- package/src/Modal/Modal.tsx +42 -0
- package/src/Modal/ModalBackdrop.tsx +72 -0
- package/src/Modal/NavDrawer.story.tsx +157 -0
- package/src/Modal/index.ts +2 -0
- package/src/Modal/styles.css +46 -0
- package/src/Popover/.gitkeep +0 -0
- package/src/Popper/Popper.story.tsx +267 -0
- package/src/Popper/Popper.tsx +149 -0
- package/src/Popper/PopperArrow.tsx +36 -0
- package/src/Popper/context.ts +9 -0
- package/src/Popper/index.ts +3 -0
- package/src/Popper/styles.css +60 -0
- package/src/Portal/Portal.tsx +20 -0
- package/src/Portal/index.ts +1 -0
- package/src/RadioButton/RadioButton.story.tsx +73 -0
- package/src/RadioButton/RadioButton.tsx +48 -0
- package/src/RadioButton/RadioGroup.tsx +56 -0
- package/src/RadioButton/context.ts +19 -0
- package/src/RadioButton/index.ts +2 -0
- package/src/SkipNav/SkipNav.tsx +16 -0
- package/src/SkipNav/index.tsx +1 -0
- package/src/Spinner/Spinner.story.tsx +30 -0
- package/src/Spinner/Spinner.tsx +112 -0
- package/src/Spinner/SpinnerButton.tsx +48 -0
- package/src/Spinner/context.ts +21 -0
- package/src/Spinner/index.ts +2 -0
- package/src/Spinner/styles.css +23 -0
- package/src/Tabs/Tab.story.tsx +78 -0
- package/src/Tabs/Tab.tsx +131 -0
- package/src/Tabs/TabList.tsx +63 -0
- package/src/Tabs/TabPanel.tsx +52 -0
- package/src/Tabs/TabPanels.tsx +30 -0
- package/src/Tabs/Tabs.tsx +47 -0
- package/src/Tabs/context.ts +30 -0
- package/src/Tabs/index.tsx +5 -0
- package/src/Tabs/scopeQuery.ts +6 -0
- package/src/Tabs/styles.css +0 -0
- package/src/Tooltip/.gitkeep +0 -0
- package/src/Tooltip/Tooltip.story.tsx +43 -0
- package/src/Tooltip/Tooltip.tsx +48 -0
- package/src/Tooltip/index.ts +1 -0
- package/src/Tooltip/stateMachine.ts +185 -0
- package/src/Tooltip/useTooltip.ts +121 -0
- package/src/hooks/index.ts +14 -0
- package/src/hooks/useAutoFocus.ts +13 -0
- package/src/hooks/useChildrenCounter.ts +50 -0
- package/src/hooks/useControlledState.ts +37 -0
- package/src/hooks/useFocusReturn.ts +23 -0
- package/src/hooks/useFocusState.ts +28 -0
- package/src/hooks/useGestureHandlers.ts +217 -0
- package/src/hooks/useId.ts +18 -0
- package/src/hooks/useMeasure.ts +33 -0
- package/src/hooks/useOnClickOutside.ts +32 -0
- package/src/hooks/useOnKeyDown.ts +18 -0
- package/src/hooks/useReducerMachine.ts +59 -0
- package/src/hooks/useRemoveBodyScroll.ts +37 -0
- package/src/hooks/useScope.ts +51 -0
- package/src/hooks/useThrottle.ts +19 -0
- package/src/index.ts +19 -0
- package/src/utils/assignRef.ts +27 -0
- package/src/utils/clamp.ts +3 -0
- package/src/utils/createSubscription.ts +16 -0
- package/src/utils/getCircularIndex.ts +7 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/rubberBandClamp.ts +25 -0
- 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);
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
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 stories = storiesOf('Components/Tooltip', module);
|
|
42
|
+
|
|
43
|
+
stories.add('controlled', () => <Example />);
|
|
@@ -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,185 @@
|
|
|
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(TooltipEventTypes.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(TooltipEventTypes.TimeComplete, undefined),
|
|
28
|
+
100
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function clearLeavingVisibleTimer() {
|
|
33
|
+
window.clearTimeout(leavingVisibleTimer);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
37
|
+
// State machine
|
|
38
|
+
|
|
39
|
+
export enum TooltipStates {
|
|
40
|
+
// Nothing goin' on
|
|
41
|
+
Idle = 'IDLE',
|
|
42
|
+
|
|
43
|
+
// We're considering showing the tooltip, but we're gonna wait a sec
|
|
44
|
+
Focused = 'FOCUSED',
|
|
45
|
+
|
|
46
|
+
// It's on!
|
|
47
|
+
Visible = 'VISIBLE',
|
|
48
|
+
|
|
49
|
+
// Focus has left, but we want to keep it visible for a sec
|
|
50
|
+
LeavingVisible = 'LEAVING_VISIBLE',
|
|
51
|
+
|
|
52
|
+
// The user clicked the tool, so we want to hide the thing, we can't just use
|
|
53
|
+
// IDLE because we need to ignore mousemove, etc.
|
|
54
|
+
Dismissed = 'DISMISSED',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export enum TooltipEventTypes {
|
|
58
|
+
Blur = 'BLUR',
|
|
59
|
+
Focus = 'FOCUS',
|
|
60
|
+
GlobalMouseMove = 'GLOBAL_MOUSE_MOVE',
|
|
61
|
+
MouseDown = 'MOUSE_DOWN',
|
|
62
|
+
MouseEnter = 'MOUSE_ENTER',
|
|
63
|
+
MouseLeave = 'MOUSE_LEAVE',
|
|
64
|
+
MouseMove = 'MOUSE_MOVE',
|
|
65
|
+
Rest = 'REST',
|
|
66
|
+
SelectWithKeyboard = 'SELECT_WITH_KEYBOARD',
|
|
67
|
+
TimeComplete = 'TIME_COMPLETE',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const subscription = createSubscription();
|
|
71
|
+
export const state = {
|
|
72
|
+
current: {
|
|
73
|
+
state: TooltipStates.Idle,
|
|
74
|
+
id: '',
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function clearContextId() {
|
|
79
|
+
state.current.id = '';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const chart: GenericStateChart<TooltipStates, TooltipEventTypes> = {
|
|
83
|
+
initial: TooltipStates.Idle,
|
|
84
|
+
states: {
|
|
85
|
+
[TooltipStates.Idle]: {
|
|
86
|
+
enter: () => {
|
|
87
|
+
clearContextId();
|
|
88
|
+
},
|
|
89
|
+
on: {
|
|
90
|
+
[TooltipEventTypes.MouseEnter]: TooltipStates.Focused,
|
|
91
|
+
[TooltipEventTypes.Focus]: TooltipStates.Visible,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
[TooltipStates.Focused]: {
|
|
95
|
+
enter: startRestTimer,
|
|
96
|
+
leave: clearRestTimer,
|
|
97
|
+
on: {
|
|
98
|
+
[TooltipEventTypes.MouseMove]: TooltipStates.Focused,
|
|
99
|
+
[TooltipEventTypes.MouseLeave]: TooltipStates.Idle,
|
|
100
|
+
[TooltipEventTypes.MouseDown]: TooltipStates.Dismissed,
|
|
101
|
+
[TooltipEventTypes.Blur]: TooltipStates.Idle,
|
|
102
|
+
[TooltipEventTypes.Rest]: TooltipStates.Visible,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
[TooltipStates.Visible]: {
|
|
106
|
+
on: {
|
|
107
|
+
[TooltipEventTypes.Focus]: TooltipStates.Focused,
|
|
108
|
+
[TooltipEventTypes.MouseEnter]: TooltipStates.Focused,
|
|
109
|
+
[TooltipEventTypes.MouseLeave]: TooltipStates.LeavingVisible,
|
|
110
|
+
[TooltipEventTypes.Blur]: TooltipStates.LeavingVisible,
|
|
111
|
+
[TooltipEventTypes.MouseDown]: TooltipStates.Dismissed,
|
|
112
|
+
[TooltipEventTypes.SelectWithKeyboard]: TooltipStates.Dismissed,
|
|
113
|
+
[TooltipEventTypes.GlobalMouseMove]: TooltipStates.LeavingVisible,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
[TooltipStates.LeavingVisible]: {
|
|
117
|
+
enter: () => {
|
|
118
|
+
startLeavingVisibleTimer();
|
|
119
|
+
},
|
|
120
|
+
leave: () => {
|
|
121
|
+
clearLeavingVisibleTimer();
|
|
122
|
+
clearContextId();
|
|
123
|
+
},
|
|
124
|
+
on: {
|
|
125
|
+
[TooltipEventTypes.MouseEnter]: TooltipStates.Visible,
|
|
126
|
+
[TooltipEventTypes.Focus]: TooltipStates.Visible,
|
|
127
|
+
[TooltipEventTypes.TimeComplete]: TooltipStates.Idle,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
[TooltipStates.Dismissed]: {
|
|
131
|
+
leave: () => {
|
|
132
|
+
clearContextId();
|
|
133
|
+
},
|
|
134
|
+
on: {
|
|
135
|
+
[TooltipEventTypes.MouseLeave]: TooltipStates.Idle,
|
|
136
|
+
[TooltipEventTypes.Blur]: TooltipStates.Idle,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
function transition(
|
|
143
|
+
currentState: typeof state['current'],
|
|
144
|
+
event: TooltipEventTypes,
|
|
145
|
+
payload?: Omit<typeof state['current'], 'state'>
|
|
146
|
+
): typeof state['current'] {
|
|
147
|
+
const currentStateValue = currentState.state;
|
|
148
|
+
const stateDef = chart.states[currentState.state];
|
|
149
|
+
const nextState = stateDef?.on?.[event];
|
|
150
|
+
|
|
151
|
+
// Really useful for debugging
|
|
152
|
+
// console.log({
|
|
153
|
+
// event,
|
|
154
|
+
// state: state.current.state,
|
|
155
|
+
// id: state.current.id,
|
|
156
|
+
// nextState,
|
|
157
|
+
// });
|
|
158
|
+
|
|
159
|
+
if (!nextState) {
|
|
160
|
+
return currentState;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (stateDef && stateDef.leave) {
|
|
164
|
+
stateDef.leave(currentStateValue, payload);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const nextStateValue = nextState;
|
|
168
|
+
const nextDef = chart.states[nextStateValue];
|
|
169
|
+
if (nextDef && nextDef.enter) {
|
|
170
|
+
nextDef.enter(nextStateValue, payload);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return { ...currentState, ...payload, state: nextStateValue };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function send<T extends TooltipEventTypes>(
|
|
177
|
+
event: T,
|
|
178
|
+
payload?: Omit<typeof state['current'], 'state'>
|
|
179
|
+
) {
|
|
180
|
+
const nextState = transition(state.current, event, payload);
|
|
181
|
+
if (state.current !== nextState) {
|
|
182
|
+
state.current = nextState;
|
|
183
|
+
subscription.notify();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
TooltipEventTypes,
|
|
10
|
+
TooltipStates,
|
|
11
|
+
} from './stateMachine';
|
|
12
|
+
|
|
13
|
+
export type ChildProps = React.HTMLAttributes<HTMLElement> &
|
|
14
|
+
RefAttributes<HTMLElement>;
|
|
15
|
+
|
|
16
|
+
export interface InjectedTooltipProps
|
|
17
|
+
extends React.HTMLAttributes<HTMLElement> {
|
|
18
|
+
anchorEl: React.RefObject<HTMLElement>;
|
|
19
|
+
visible: boolean;
|
|
20
|
+
children?: React.ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useTooltip(
|
|
24
|
+
childProps: ChildProps,
|
|
25
|
+
childRef: React.Ref<HTMLElement> | undefined,
|
|
26
|
+
tooltipProps: React.HTMLAttributes<HTMLElement> & { label?: React.ReactNode }
|
|
27
|
+
): [ChildProps, InjectedTooltipProps] {
|
|
28
|
+
const {
|
|
29
|
+
onMouseEnter,
|
|
30
|
+
onMouseLeave,
|
|
31
|
+
onMouseMove,
|
|
32
|
+
onMouseDown,
|
|
33
|
+
onKeyDown,
|
|
34
|
+
onFocus,
|
|
35
|
+
onBlur,
|
|
36
|
+
} = childProps;
|
|
37
|
+
const anchorEl = useRef<HTMLElement>(null);
|
|
38
|
+
const [visible, setVisible] = useState(false);
|
|
39
|
+
const id = useId('');
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
subscription.subscribe(() => {
|
|
43
|
+
setVisible(
|
|
44
|
+
(state.current.state === TooltipStates.Visible ||
|
|
45
|
+
state.current.state === TooltipStates.LeavingVisible) &&
|
|
46
|
+
state.current.id === id
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
}, [id]);
|
|
50
|
+
|
|
51
|
+
function handleMouseEnter() {
|
|
52
|
+
send(TooltipEventTypes.MouseEnter, { id });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function handleMouseMove() {
|
|
56
|
+
send(TooltipEventTypes.MouseMove, { id });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function handleMouseLeave() {
|
|
60
|
+
send(TooltipEventTypes.MouseLeave);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function handleMouseDown() {
|
|
64
|
+
// Allow quick click from one tool to another
|
|
65
|
+
if (state.current.id === id) {
|
|
66
|
+
send(TooltipEventTypes.MouseDown);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function handleFocus() {
|
|
71
|
+
send(TooltipEventTypes.Focus, { id });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function handleBlur() {
|
|
75
|
+
// Allow quick click from one tool to another
|
|
76
|
+
if (state.current.id === id) {
|
|
77
|
+
send(TooltipEventTypes.Blur, undefined);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function handleKeyDown(event: React.KeyboardEvent<HTMLElement>) {
|
|
82
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
83
|
+
send(TooltipEventTypes.SelectWithKeyboard);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const {
|
|
88
|
+
label: children,
|
|
89
|
+
onMouseEnter: tooltipOnMouseEnter,
|
|
90
|
+
onMouseLeave: tooltipOnMouseLeave,
|
|
91
|
+
onMouseMove: tooltipOnMouseMove,
|
|
92
|
+
...otherTooltipProps
|
|
93
|
+
} = tooltipProps;
|
|
94
|
+
|
|
95
|
+
const tooltipId = `tooltip-${id}`;
|
|
96
|
+
return [
|
|
97
|
+
{
|
|
98
|
+
...childProps,
|
|
99
|
+
ref: assignMultipleRefs(childRef, anchorEl),
|
|
100
|
+
...(visible && { 'aria-describedby': tooltipId }),
|
|
101
|
+
onMouseEnter: wrapEvent(onMouseEnter, handleMouseEnter),
|
|
102
|
+
onMouseLeave: wrapEvent(onMouseLeave, handleMouseLeave),
|
|
103
|
+
onMouseMove: wrapEvent(onMouseMove, handleMouseMove),
|
|
104
|
+
onMouseDown: wrapEvent(onMouseDown, handleMouseDown),
|
|
105
|
+
onFocus: wrapEvent(onFocus, handleFocus),
|
|
106
|
+
onBlur: wrapEvent(onBlur, handleBlur),
|
|
107
|
+
onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: tooltipId,
|
|
111
|
+
anchorEl,
|
|
112
|
+
visible,
|
|
113
|
+
children,
|
|
114
|
+
onMouseEnter: wrapEvent(tooltipOnMouseEnter, handleMouseEnter),
|
|
115
|
+
onMouseLeave: wrapEvent(tooltipOnMouseLeave, handleMouseLeave),
|
|
116
|
+
onMouseMove: wrapEvent(tooltipOnMouseMove, handleMouseMove),
|
|
117
|
+
role: 'tooltip',
|
|
118
|
+
...otherTooltipProps,
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
import { wrapEvent, CustomEventHandler } from '../utils';
|
|
3
|
+
|
|
4
|
+
export function useControlledState<
|
|
5
|
+
V,
|
|
6
|
+
E extends React.SyntheticEvent<any>,
|
|
7
|
+
H extends unknown[]
|
|
8
|
+
>(
|
|
9
|
+
valueProp: V | undefined,
|
|
10
|
+
onChangeProp: CustomEventHandler<E, H> | undefined,
|
|
11
|
+
defaultValue: V,
|
|
12
|
+
defaultOnChange: (
|
|
13
|
+
setValue: React.Dispatch<React.SetStateAction<V>>
|
|
14
|
+
) => CustomEventHandler<E, H>
|
|
15
|
+
): [
|
|
16
|
+
V,
|
|
17
|
+
CustomEventHandler<E, H> | undefined,
|
|
18
|
+
React.Dispatch<React.SetStateAction<V>>
|
|
19
|
+
] {
|
|
20
|
+
const isControlled = useRef(valueProp !== undefined);
|
|
21
|
+
const [valueState, setValueState] = useState<V>(defaultValue);
|
|
22
|
+
|
|
23
|
+
if (isControlled.current) {
|
|
24
|
+
if (valueProp === undefined) {
|
|
25
|
+
console.warn('Trying to change from controlled to uncontrolled.');
|
|
26
|
+
}
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
28
|
+
return [valueProp!, onChangeProp, setValueState];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return [
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
33
|
+
valueState!,
|
|
34
|
+
wrapEvent(onChangeProp, defaultOnChange(setValueState)),
|
|
35
|
+
setValueState,
|
|
36
|
+
];
|
|
37
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useFocusReturn(open: boolean) {
|
|
4
|
+
const previousFocusRef = useRef<Element | null>(null);
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (open) {
|
|
8
|
+
// once opened, keep track of the element that triggered
|
|
9
|
+
// the Modal opening
|
|
10
|
+
previousFocusRef.current = document.activeElement;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return () => {
|
|
14
|
+
// on unmount, return focus to that element
|
|
15
|
+
const previousFocus = previousFocusRef.current;
|
|
16
|
+
requestAnimationFrame(() => {
|
|
17
|
+
if (previousFocus) {
|
|
18
|
+
(previousFocus as HTMLElement).focus();
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
}, [open]);
|
|
23
|
+
}
|