@adamjanicki/ui-extended 1.0.0
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/LICENSE +21 -0
- package/README.md +38 -0
- package/components/Autocomplete/Autocomplete.d.ts +89 -0
- package/components/Autocomplete/Autocomplete.js +133 -0
- package/components/Autocomplete/index.d.ts +2 -0
- package/components/Autocomplete/index.js +2 -0
- package/components/Popover/Popover.d.ts +45 -0
- package/components/Popover/Popover.js +39 -0
- package/components/Popover/index.d.ts +2 -0
- package/components/Popover/index.js +2 -0
- package/components/Tooltip/Tooltip.d.ts +36 -0
- package/components/Tooltip/Tooltip.js +37 -0
- package/components/Tooltip/index.d.ts +2 -0
- package/components/Tooltip/index.js +2 -0
- package/hooks/index.d.ts +1 -0
- package/hooks/index.js +1 -0
- package/hooks/useTheme.d.ts +24 -0
- package/hooks/useTheme.js +27 -0
- package/index.d.ts +3 -0
- package/index.js +4 -0
- package/package.json +44 -0
- package/style.css +42 -0
- package/utils/types.d.ts +8 -0
- package/utils/types.js +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Adam Janicki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @adamjanicki/ui-extended
|
|
2
|
+
|
|
3
|
+
**Warning: use at own risk, these are mainly for personal use across my other sites, so while I strive to write good code, there may be bugs!**
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @adamjanicki/ui-extended
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// Add here
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Importing CSS
|
|
18
|
+
|
|
19
|
+
Unfortunately, there was no great way to handle CSS. I often hate how large libraries make it extremely difficult to override CSS without using `!important`, or using inline styles. So, I've decided to just import the CSS directly into your project. Here's an example of how to do it:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import React from "react";
|
|
23
|
+
import ReactDOM from "react-dom/client";
|
|
24
|
+
// Make sure to import this first so your styles take priority!
|
|
25
|
+
import "@adamjanicki/ui-extended/style.css";
|
|
26
|
+
// All your other global styles can go here!
|
|
27
|
+
import "src/css/style.css";
|
|
28
|
+
import App from "src/App";
|
|
29
|
+
|
|
30
|
+
const root = ReactDOM.createRoot(
|
|
31
|
+
document.getElementById("root") as HTMLElement
|
|
32
|
+
);
|
|
33
|
+
root.render(
|
|
34
|
+
<React.StrictMode>
|
|
35
|
+
<App />
|
|
36
|
+
</React.StrictMode>
|
|
37
|
+
);
|
|
38
|
+
```
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Popover from "../Popover/Popover";
|
|
3
|
+
import { IconInput } from "@adamjanicki/ui";
|
|
4
|
+
interface Props<T> {
|
|
5
|
+
/**
|
|
6
|
+
* The value of the input field
|
|
7
|
+
*/
|
|
8
|
+
value: string;
|
|
9
|
+
/**
|
|
10
|
+
* Callback for when the input field changes
|
|
11
|
+
* @param event standard React ChangeEvent
|
|
12
|
+
*/
|
|
13
|
+
onInputChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Callback for when an option is selected
|
|
16
|
+
* @param value selected value
|
|
17
|
+
*/
|
|
18
|
+
onSelect: (value: T) => void;
|
|
19
|
+
/**
|
|
20
|
+
* The list of available options
|
|
21
|
+
*/
|
|
22
|
+
options: T[] | readonly T[];
|
|
23
|
+
/**
|
|
24
|
+
* Predicate to filter options
|
|
25
|
+
* @param option current option
|
|
26
|
+
* @returns true if the option should be displayed
|
|
27
|
+
*/
|
|
28
|
+
filterOption?: (option: T) => boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Render function for the option
|
|
31
|
+
* @param option current option
|
|
32
|
+
* @returns node to render for the option
|
|
33
|
+
*/
|
|
34
|
+
renderOption?: (option: T) => React.ReactNode;
|
|
35
|
+
/**
|
|
36
|
+
* Node to render when no options are available
|
|
37
|
+
*/
|
|
38
|
+
noOptionsNode?: React.ReactNode;
|
|
39
|
+
/**
|
|
40
|
+
* Classname to apply to the root element
|
|
41
|
+
*/
|
|
42
|
+
className?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Style to apply to the root element
|
|
45
|
+
*/
|
|
46
|
+
style?: React.CSSProperties;
|
|
47
|
+
/**
|
|
48
|
+
* Group options by a string
|
|
49
|
+
* @param option current option
|
|
50
|
+
* @returns String to group by
|
|
51
|
+
*/
|
|
52
|
+
groupBy?: (option: T) => string;
|
|
53
|
+
/**
|
|
54
|
+
* Render function for the group
|
|
55
|
+
* @param group name
|
|
56
|
+
* @returns node to render for the group
|
|
57
|
+
*/
|
|
58
|
+
renderGroup?: (group: string) => React.ReactNode;
|
|
59
|
+
/**
|
|
60
|
+
* Allow free text input
|
|
61
|
+
*/
|
|
62
|
+
freeSolo?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Focus on the input field after selecting an option
|
|
65
|
+
*/
|
|
66
|
+
focusOnSelect?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Props for the input field
|
|
69
|
+
*/
|
|
70
|
+
InputProps?: React.ComponentProps<typeof IconInput>;
|
|
71
|
+
/**
|
|
72
|
+
* Props for the popover
|
|
73
|
+
*/
|
|
74
|
+
popoverProps?: Partial<React.ComponentProps<typeof Popover>>;
|
|
75
|
+
/**
|
|
76
|
+
* Props for the list element
|
|
77
|
+
*/
|
|
78
|
+
listProps?: React.ComponentProps<"ul">;
|
|
79
|
+
/**
|
|
80
|
+
* Props for the list item elements
|
|
81
|
+
*/
|
|
82
|
+
listItemProps?: React.ComponentProps<"li">;
|
|
83
|
+
/**
|
|
84
|
+
* Footer node to render at the bottom of the popover
|
|
85
|
+
*/
|
|
86
|
+
footer?: React.ReactNode;
|
|
87
|
+
}
|
|
88
|
+
declare const Autocomplete: <T>(props: Props<T>) => JSX.Element;
|
|
89
|
+
export default Autocomplete;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
13
|
+
var t = {};
|
|
14
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
15
|
+
t[p] = s[p];
|
|
16
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
17
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
18
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
19
|
+
t[p[i]] = s[p[i]];
|
|
20
|
+
}
|
|
21
|
+
return t;
|
|
22
|
+
};
|
|
23
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
24
|
+
import React from "react";
|
|
25
|
+
import Popover from "../Popover/Popover";
|
|
26
|
+
import ClickOutside from "@adamjanicki/ui/components/ClickOutside";
|
|
27
|
+
import { IconInput } from "@adamjanicki/ui";
|
|
28
|
+
import { classNames } from "@adamjanicki/ui/utils/util";
|
|
29
|
+
var defaultRenderOption = function (option) { return (_jsx("div", { className: "ajui-autocomplete-default-rendering", children: "".concat(option) })); };
|
|
30
|
+
var Autocomplete = function (props) {
|
|
31
|
+
var _a, _b;
|
|
32
|
+
var options = props.options, _c = props.renderOption, renderOption = _c === void 0 ? defaultRenderOption : _c, _d = props.filterOption, filterOption = _d === void 0 ? function () { return true; } : _d, groupBy = props.groupBy, renderGroup = props.renderGroup, noOptionsNode = props.noOptionsNode, _e = props.InputProps, InputProps = _e === void 0 ? {} : _e, _f = props.freeSolo, freeSolo = _f === void 0 ? false : _f, _g = props.focusOnSelect, focusOnSelect = _g === void 0 ? true : _g, value = props.value, onInputChange = props.onInputChange, onSelect = props.onSelect, popoverProps = props.popoverProps, footer = props.footer, _h = props.listItemProps, listItemProps = _h === void 0 ? {} : _h, _j = props.listProps, listProps = _j === void 0 ? {} : _j, rest = __rest(props, ["options", "renderOption", "filterOption", "groupBy", "renderGroup", "noOptionsNode", "InputProps", "freeSolo", "focusOnSelect", "value", "onInputChange", "onSelect", "popoverProps", "footer", "listItemProps", "listProps"]);
|
|
33
|
+
var onRef = React.useRef(null);
|
|
34
|
+
var nextRef = React.useRef(null);
|
|
35
|
+
var prevRef = React.useRef(null);
|
|
36
|
+
var _k = React.useState(), on = _k[0], setOn = _k[1];
|
|
37
|
+
var filteredOptions = options.filter(filterOption);
|
|
38
|
+
var groupMap = new Map();
|
|
39
|
+
if (groupBy) {
|
|
40
|
+
var uniqueGroups_1 = [];
|
|
41
|
+
filteredOptions.forEach(function (option) {
|
|
42
|
+
var group = groupBy(option);
|
|
43
|
+
if (!uniqueGroups_1.includes(group)) {
|
|
44
|
+
uniqueGroups_1.push(group);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
var offset_1 = 0;
|
|
48
|
+
filteredOptions = uniqueGroups_1
|
|
49
|
+
.map(function (group) {
|
|
50
|
+
var filtered = filteredOptions.filter(function (option) { return groupBy(option) === group; });
|
|
51
|
+
groupMap.set(offset_1, group);
|
|
52
|
+
offset_1 += filtered.length;
|
|
53
|
+
return filtered;
|
|
54
|
+
})
|
|
55
|
+
.flat();
|
|
56
|
+
}
|
|
57
|
+
if (freeSolo && value.length > 0 && filteredOptions.length === 0) {
|
|
58
|
+
filteredOptions.push(value);
|
|
59
|
+
}
|
|
60
|
+
var handleChange = function (v) {
|
|
61
|
+
onSelect(v);
|
|
62
|
+
var current = inputRef.current;
|
|
63
|
+
closeMenu();
|
|
64
|
+
if (focusOnSelect)
|
|
65
|
+
return current === null || current === void 0 ? void 0 : current.focus();
|
|
66
|
+
};
|
|
67
|
+
var closeMenu = function () {
|
|
68
|
+
setOn(undefined);
|
|
69
|
+
setOpen(false);
|
|
70
|
+
};
|
|
71
|
+
var openMenu = function () {
|
|
72
|
+
setOpen(true);
|
|
73
|
+
};
|
|
74
|
+
var handleKeys = function (_a) {
|
|
75
|
+
var code = _a.code;
|
|
76
|
+
if (code === "Escape") {
|
|
77
|
+
closeMenu();
|
|
78
|
+
}
|
|
79
|
+
var modulo = filteredOptions.length;
|
|
80
|
+
if (code === "Enter") {
|
|
81
|
+
var current = onRef.current;
|
|
82
|
+
if (modulo > 0 && current) {
|
|
83
|
+
return current.click();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (modulo > 0 && code === "ArrowDown") {
|
|
87
|
+
var newOn = ((on !== undefined ? on : -1) + 1) % modulo;
|
|
88
|
+
setOn(newOn);
|
|
89
|
+
if (nextRef.current) {
|
|
90
|
+
nextRef.current.scrollIntoView({
|
|
91
|
+
block: "nearest",
|
|
92
|
+
behavior: "smooth",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (modulo > 0 && code === "ArrowUp") {
|
|
97
|
+
var newOn = ((on !== undefined ? on : 0) - 1 + modulo) % modulo;
|
|
98
|
+
setOn(newOn);
|
|
99
|
+
if (prevRef.current) {
|
|
100
|
+
prevRef.current.scrollIntoView({
|
|
101
|
+
block: "nearest",
|
|
102
|
+
behavior: "smooth",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
var _l = React.useState(false), open = _l[0], setOpen = _l[1];
|
|
108
|
+
var inputContainerRef = React.useRef(null);
|
|
109
|
+
var inputRef = React.useRef(null);
|
|
110
|
+
var popoverOpen = open && (filteredOptions.length > 0 || value.length > 0);
|
|
111
|
+
return (_jsx(ClickOutside, { onClickOutside: closeMenu, children: _jsxs("div", __assign({}, rest, { onKeyUp: function (e) { return handleKeys(e); }, children: [_jsx(IconInput, __assign({}, InputProps, { ref: inputContainerRef, inputProps: __assign(__assign({}, (InputProps.inputProps || {})), { value: value, onChange: function (e) {
|
|
112
|
+
setOn(undefined);
|
|
113
|
+
onInputChange(e);
|
|
114
|
+
if (e.target.value) {
|
|
115
|
+
!open && openMenu();
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
var current = inputRef.current;
|
|
119
|
+
current && current.focus();
|
|
120
|
+
}
|
|
121
|
+
}, onClick: function () {
|
|
122
|
+
!open && openMenu();
|
|
123
|
+
}, ref: inputRef, autoComplete: "off" }) })), _jsxs(Popover, __assign({}, popoverProps, { open: popoverOpen, triggerRef: inputContainerRef, style: __assign(__assign({ zIndex: 100 }, ((popoverProps === null || popoverProps === void 0 ? void 0 : popoverProps.style) || {})), { padding: 0, margin: 0, width: (_b = (_a = inputContainerRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) !== null && _b !== void 0 ? _b : 0 }), className: "ajui-autocomplete-popover", children: [_jsx("ul", __assign({}, listProps, { className: classNames("ajui-autocomplete-ul", listProps.className), children: filteredOptions.length
|
|
124
|
+
? filteredOptions.map(function (option, index) {
|
|
125
|
+
var group = groupMap.get(index);
|
|
126
|
+
return (_jsxs(React.Fragment, { children: [group && ((renderGroup === null || renderGroup === void 0 ? void 0 : renderGroup(group)) || group), _jsx("li", __assign({}, listItemProps, { ref: on === index ? onRef : null, onMouseEnter: function () { return setOn(index); }, className: classNames("ajui-autocomplete-li", on === index
|
|
127
|
+
? "ajui-autocomplete-on-option"
|
|
128
|
+
: undefined, listItemProps.className), onClick: function () { return handleChange(option); }, children: renderOption(option) }))] }, index));
|
|
129
|
+
})
|
|
130
|
+
: !freeSolo &&
|
|
131
|
+
(noOptionsNode || defaultRenderOption("No results found")) })), footer] }))] })) }));
|
|
132
|
+
};
|
|
133
|
+
export default Autocomplete;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type Placement } from "@floating-ui/react";
|
|
3
|
+
type Props = {
|
|
4
|
+
/**
|
|
5
|
+
* Children to render inside the popover.
|
|
6
|
+
*/
|
|
7
|
+
children: React.ReactNode | React.ReactNode[];
|
|
8
|
+
/**
|
|
9
|
+
* The trigger ref for the element to position the popover over.
|
|
10
|
+
*/
|
|
11
|
+
triggerRef: React.RefObject<HTMLElement>;
|
|
12
|
+
/**
|
|
13
|
+
* Whether the popover is open.
|
|
14
|
+
*/
|
|
15
|
+
open: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The placement of the popover relative to the trigger element.
|
|
18
|
+
* @default "bottom"
|
|
19
|
+
*/
|
|
20
|
+
placement?: Placement;
|
|
21
|
+
/**
|
|
22
|
+
* Additional styles to apply to the popover container.
|
|
23
|
+
*/
|
|
24
|
+
style?: React.CSSProperties;
|
|
25
|
+
/**
|
|
26
|
+
* Additional classes to apply to the popover container.
|
|
27
|
+
*/
|
|
28
|
+
className?: string;
|
|
29
|
+
/**
|
|
30
|
+
* The offset of the popover relative to the trigger element.
|
|
31
|
+
* @default 0
|
|
32
|
+
*/
|
|
33
|
+
offset?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Callback function to execute when the popover is closed.
|
|
36
|
+
*/
|
|
37
|
+
onClose?: () => void;
|
|
38
|
+
/**
|
|
39
|
+
* Whether to return focus to the trigger element when the popover is closed
|
|
40
|
+
* by pressing the escape key.
|
|
41
|
+
*/
|
|
42
|
+
returnFocusOnEscape?: boolean;
|
|
43
|
+
};
|
|
44
|
+
declare const Popover: (props: Props) => JSX.Element | null;
|
|
45
|
+
export default Popover;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import { useFloating, autoUpdate, offset, useDismiss, } from "@floating-ui/react";
|
|
14
|
+
var Popover = function (props) {
|
|
15
|
+
var triggerRef = props.triggerRef, open = props.open, _a = props.placement, placement = _a === void 0 ? "bottom" : _a, style = props.style, _b = props.offset, placementOffset = _b === void 0 ? 0 : _b, className = props.className, children = props.children, onClose = props.onClose, _c = props.returnFocusOnEscape, returnFocusOnEscape = _c === void 0 ? true : _c;
|
|
16
|
+
var handleOnClose = function () {
|
|
17
|
+
var _a, _b;
|
|
18
|
+
if (!returnFocusOnEscape)
|
|
19
|
+
(_b = (_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.blur) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
20
|
+
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
21
|
+
};
|
|
22
|
+
var middleware = [offset(placementOffset)];
|
|
23
|
+
var _d = useFloating({
|
|
24
|
+
elements: { reference: triggerRef.current },
|
|
25
|
+
open: open,
|
|
26
|
+
onOpenChange: function (open) {
|
|
27
|
+
if (!open)
|
|
28
|
+
handleOnClose();
|
|
29
|
+
},
|
|
30
|
+
placement: placement,
|
|
31
|
+
whileElementsMounted: autoUpdate,
|
|
32
|
+
middleware: middleware,
|
|
33
|
+
}), floatingStyles = _d.floatingStyles, context = _d.context, refs = _d.refs;
|
|
34
|
+
useDismiss(context);
|
|
35
|
+
if (!open)
|
|
36
|
+
return null;
|
|
37
|
+
return (_jsx("div", { ref: refs.setFloating, style: __assign(__assign(__assign({}, (style || {})), floatingStyles), { outline: "none" }), className: className, children: children }));
|
|
38
|
+
};
|
|
39
|
+
export default Popover;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Placement } from "@floating-ui/react";
|
|
2
|
+
type Props = {
|
|
3
|
+
/**
|
|
4
|
+
* Children to render inside the tooltip container.
|
|
5
|
+
*/
|
|
6
|
+
tooltipContent: React.ReactNode | React.ReactNode[];
|
|
7
|
+
/**
|
|
8
|
+
* The element to attach the tooltip to.
|
|
9
|
+
* **IMPORTANT**: This must be able to hold a ref.
|
|
10
|
+
*/
|
|
11
|
+
children: React.ReactElement;
|
|
12
|
+
/**
|
|
13
|
+
* The placement of the popover relative to the trigger element.
|
|
14
|
+
* @default "bottom"
|
|
15
|
+
*/
|
|
16
|
+
placement?: Placement;
|
|
17
|
+
/**
|
|
18
|
+
* Additional styles to apply to the tooltip container.
|
|
19
|
+
*/
|
|
20
|
+
style?: React.CSSProperties;
|
|
21
|
+
/**
|
|
22
|
+
* Additional classes to apply to the tooltip container.
|
|
23
|
+
*/
|
|
24
|
+
className?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The offset of the popover relative to the trigger element.
|
|
27
|
+
* @default 0
|
|
28
|
+
*/
|
|
29
|
+
offset?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Whether to disable the flip behavior of the tooltip.
|
|
32
|
+
*/
|
|
33
|
+
disableFlip?: boolean;
|
|
34
|
+
};
|
|
35
|
+
declare const Tooltip: (props: Props) => JSX.Element;
|
|
36
|
+
export default Tooltip;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
import { cloneElement, useState } from "react";
|
|
14
|
+
import { useFloating, useHover, useInteractions, flip, safePolygon, autoUpdate, offset, } from "@floating-ui/react";
|
|
15
|
+
var Tooltip = function (props) {
|
|
16
|
+
var children = props.children, content = props.tooltipContent, _a = props.placement, placement = _a === void 0 ? "bottom" : _a, style = props.style, _b = props.offset, placementOffset = _b === void 0 ? 0 : _b, className = props.className, _c = props.disableFlip, disableFlip = _c === void 0 ? false : _c;
|
|
17
|
+
var _d = useState(false), open = _d[0], setOpen = _d[1];
|
|
18
|
+
var middleware = [offset(placementOffset)];
|
|
19
|
+
if (!disableFlip) {
|
|
20
|
+
middleware.push(flip());
|
|
21
|
+
}
|
|
22
|
+
var _e = useFloating({
|
|
23
|
+
open: open,
|
|
24
|
+
onOpenChange: setOpen,
|
|
25
|
+
middleware: middleware,
|
|
26
|
+
whileElementsMounted: autoUpdate,
|
|
27
|
+
placement: placement,
|
|
28
|
+
}), refs = _e.refs, floatingStyles = _e.floatingStyles, context = _e.context;
|
|
29
|
+
var hover = useHover(context, {
|
|
30
|
+
delay: { open: 200, close: 100 },
|
|
31
|
+
handleClose: safePolygon(),
|
|
32
|
+
mouseOnly: true,
|
|
33
|
+
});
|
|
34
|
+
var _f = useInteractions([hover]), getReferenceProps = _f.getReferenceProps, getFloatingProps = _f.getFloatingProps;
|
|
35
|
+
return (_jsxs(_Fragment, { children: [cloneElement(children, __assign({ ref: refs.setReference }, getReferenceProps())), open && (_jsx("div", __assign({ ref: refs.setFloating, style: __assign(__assign({}, (style || {})), floatingStyles) }, getFloatingProps(), { className: className, children: content })))] }));
|
|
36
|
+
};
|
|
37
|
+
export default Tooltip;
|
package/hooks/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useTheme";
|
package/hooks/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useTheme";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type Theme = "light" | "dark";
|
|
2
|
+
export type ThemePreference = Theme | "system";
|
|
3
|
+
export type ThemePreferenceStore = {
|
|
4
|
+
/**
|
|
5
|
+
* The current theme preference.
|
|
6
|
+
*/
|
|
7
|
+
preference: ThemePreference;
|
|
8
|
+
/**
|
|
9
|
+
* Set the theme preference.
|
|
10
|
+
*
|
|
11
|
+
* @param preference the new theme preference
|
|
12
|
+
*/
|
|
13
|
+
setPreference: (preference: ThemePreference) => void;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A hook for getting and setting the current theme preference.
|
|
17
|
+
*/
|
|
18
|
+
export declare const useThemePreference: any;
|
|
19
|
+
/**
|
|
20
|
+
* A hook for getting the current theme
|
|
21
|
+
*
|
|
22
|
+
* @returns The current theme, either "light" or "dark"
|
|
23
|
+
*/
|
|
24
|
+
export declare const useTheme: () => Theme;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
import { createJSONStorage, persist } from "zustand/middleware";
|
|
3
|
+
import { useMediaQuery } from "@adamjanicki/ui";
|
|
4
|
+
/**
|
|
5
|
+
* A hook for getting and setting the current theme preference.
|
|
6
|
+
*/
|
|
7
|
+
export var useThemePreference = create(persist(function (set) { return ({
|
|
8
|
+
preference: "system",
|
|
9
|
+
setPreference: function (preference) { return set({ preference: preference }); },
|
|
10
|
+
}); }, {
|
|
11
|
+
name: "theme-preference-store",
|
|
12
|
+
storage: createJSONStorage(function () { return localStorage; }),
|
|
13
|
+
}));
|
|
14
|
+
/**
|
|
15
|
+
* A hook for getting the current theme
|
|
16
|
+
*
|
|
17
|
+
* @returns The current theme, either "light" or "dark"
|
|
18
|
+
*/
|
|
19
|
+
export var useTheme = function () {
|
|
20
|
+
var prefersDark = useMediaQuery({ query: "(prefers-color-scheme: dark)" });
|
|
21
|
+
var preference = useThemePreference().preference;
|
|
22
|
+
return preference === "system"
|
|
23
|
+
? prefersDark
|
|
24
|
+
? "dark"
|
|
25
|
+
: "light"
|
|
26
|
+
: preference;
|
|
27
|
+
};
|
package/index.d.ts
ADDED
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adamjanicki/ui-extended",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "More advanced UI components and hooks for React in TypeScript",
|
|
5
|
+
"main": "./index.js",
|
|
6
|
+
"types": "./index.d.ts",
|
|
7
|
+
"author": "Adam Janicki",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/adamjanicki2/ui-extended.git"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc; cp src/style.css ./style.css",
|
|
15
|
+
"clean": "./clean.sh",
|
|
16
|
+
"dev": "nodemon --watch src --ext ts,tsx,css --exec \"npm run build\"",
|
|
17
|
+
"lint": "eslint --ext .ts,.tsx src",
|
|
18
|
+
"prepare": "npm run lint; npm run build"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react": ">=18",
|
|
22
|
+
"react-dom": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@adamjanicki/ui": ">=1.0.3",
|
|
26
|
+
"@floating-ui/react": ">=0.26.17"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
|
30
|
+
"@typescript-eslint/parser": "^6.11.0",
|
|
31
|
+
"eslint": "^8.53.0",
|
|
32
|
+
"eslint-config-prettier": "^9.0.0",
|
|
33
|
+
"eslint-plugin-react": "^7.33.2",
|
|
34
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
35
|
+
"nodemon": "^3.1.4",
|
|
36
|
+
"typescript": "^5.2.2"
|
|
37
|
+
},
|
|
38
|
+
"eslintConfig": {
|
|
39
|
+
"extends": [
|
|
40
|
+
"react-app",
|
|
41
|
+
"react-app/jest"
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
}
|
package/style.css
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
@media (hover: hover) {
|
|
2
|
+
.ajui-autocomplete-on-option,
|
|
3
|
+
.ajui-autocomplete-option:hover {
|
|
4
|
+
background-color: var(--ajui-light-gray);
|
|
5
|
+
}
|
|
6
|
+
[data-theme="dark"] .ajui-autocomplete-on-option,
|
|
7
|
+
[data-theme="dark"] .ajui-autocomplete-option:hover {
|
|
8
|
+
background-color: var(--ajui-darkest-gray);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.ajui-autocomplete-popover {
|
|
13
|
+
background-color: var(--ajui-default-background);
|
|
14
|
+
font-size: 1rem;
|
|
15
|
+
font-weight: 400;
|
|
16
|
+
border: 1px solid var(--ajui-default-border);
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
border-radius: var(--ajui-rounded-radius);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.ajui-autocomplete-ul {
|
|
22
|
+
overflow: scroll;
|
|
23
|
+
max-height: 300px;
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
padding: 0.25rem;
|
|
27
|
+
margin: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.ajui-autocomplete-default-rendering {
|
|
31
|
+
padding: 0.5rem 1rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.ajui-autocomplete-li {
|
|
35
|
+
display: flex;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
border-radius: var(--ajui-rounded-radius);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.ajui-autocomplete-li > :first-child {
|
|
41
|
+
border-radius: var(--ajui-rounded-radius);
|
|
42
|
+
}
|
package/utils/types.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The type of corner to display, controlling the border radius property.
|
|
3
|
+
*/
|
|
4
|
+
export type CornerType = "pill" | "rounded" | "sharp";
|
|
5
|
+
/**
|
|
6
|
+
* The type of message associated with a piece of content.
|
|
7
|
+
*/
|
|
8
|
+
export type ContentType = "success" | "warning" | "error" | "info" | "static";
|
package/utils/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|