@optigrit/optigrit-ui 0.0.1
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/bin/cli.js +99 -0
- package/dist/Input-CL3wQvR_.d.ts +40 -0
- package/dist/ThemeProvider-D_1vZWeu.d.ts +17 -0
- package/dist/chunk-2LJSVQ6B.js +465 -0
- package/dist/chunk-KBOYMK4Y.js +76 -0
- package/dist/chunk-MCQS3QNN.js +8 -0
- package/dist/chunk-U65NGM6F.js +48 -0
- package/dist/components/index.d.ts +69 -0
- package/dist/components/index.js +575 -0
- package/dist/core/index.d.ts +41 -0
- package/dist/core/index.js +14 -0
- package/dist/hooks/index.d.ts +31 -0
- package/dist/hooks/index.js +12 -0
- package/dist/theme/index.d.ts +7 -0
- package/dist/theme/index.js +12 -0
- package/index.css +131 -0
- package/package.json +91 -0
- package/tailwind.preset.js +87 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
/** Published package name (scoped). Keep in sync with package.json "name". */
|
|
10
|
+
const PKG = '@optigrit/optigrit-ui';
|
|
11
|
+
const PKG_NODE_MODULES = `./node_modules/${PKG}`;
|
|
12
|
+
|
|
13
|
+
async function init() {
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
if (args[0] !== 'init') {
|
|
16
|
+
console.log(`Usage: npx optigrit-ui init or npx ${PKG} init`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const configNames = ['tailwind.config.js', 'tailwind.config.cjs', 'tailwind.config.mjs', 'tailwind.config.ts'];
|
|
21
|
+
const configPath = configNames.map(name => path.resolve(process.cwd(), name)).find(fs.existsSync);
|
|
22
|
+
|
|
23
|
+
if (!configPath) {
|
|
24
|
+
console.error('❌ Could not find tailwind.config file in the root directory.');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
let content = fs.readFileSync(configPath, 'utf8');
|
|
30
|
+
|
|
31
|
+
const legacyPresetPaths =
|
|
32
|
+
content.includes('./node_modules/optigrit-ui/tailwind.preset') ||
|
|
33
|
+
content.includes('optigrit-ui/tailwind.preset');
|
|
34
|
+
const scopedPresetPaths =
|
|
35
|
+
content.includes(`${PKG_NODE_MODULES}/tailwind.preset`) ||
|
|
36
|
+
content.includes(`${PKG}/tailwind.preset`);
|
|
37
|
+
|
|
38
|
+
// Update or add import statement
|
|
39
|
+
if (legacyPresetPaths || scopedPresetPaths) {
|
|
40
|
+
content = content.replace(/\.\/node_modules\/optigrit-ui\/tailwind\.preset/g, `${PKG}/preset`);
|
|
41
|
+
content = content.replace(/optigrit-ui\/tailwind\.preset/g, `${PKG}/preset`);
|
|
42
|
+
content = content.replace(
|
|
43
|
+
new RegExp(`\\.\\/node_modules\\/${PKG.replace(/\//g, '\\/')}\\/tailwind\\.preset`, 'g'),
|
|
44
|
+
`${PKG}/preset`,
|
|
45
|
+
);
|
|
46
|
+
content = content.replace(new RegExp(`${PKG.replace(/\//g, '\\/')}\\/tailwind\\.preset`, 'g'), `${PKG}/preset`);
|
|
47
|
+
} else if (!content.includes(`${PKG}/preset`)) {
|
|
48
|
+
content = `import preset from '${PKG}/preset'\n` + content;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const legacyContentGlob = './node_modules/optigrit-ui/**/*.{js,jsx,ts,tsx}';
|
|
52
|
+
const scopedContentGlob = `${PKG_NODE_MODULES}/**/*.{js,jsx,ts,tsx}`;
|
|
53
|
+
|
|
54
|
+
// Modify content array
|
|
55
|
+
if (!content.includes(legacyContentGlob) && !content.includes(scopedContentGlob)) {
|
|
56
|
+
content = content.replace(/content:\s*\[([\s\S]*?)\]/, (match, p1) => {
|
|
57
|
+
return `content: [\n "${scopedContentGlob}",\n ${p1.trim().replace(/\n\s*/g, '\n ')}\n ]`;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Modify presets array
|
|
62
|
+
if (!content.includes('presets:')) {
|
|
63
|
+
// If module.exports exists, inject presets array inside
|
|
64
|
+
if (content.includes('module.exports = {')) {
|
|
65
|
+
content = content.replace(/module\.exports\s*=\s*\{/, "module.exports = {\n presets: [preset],");
|
|
66
|
+
} else if (content.includes('export default {')) {
|
|
67
|
+
content = content.replace(/export\s+default\s*\{/, "export default {\n presets: [preset],");
|
|
68
|
+
}
|
|
69
|
+
} else if (!content.includes('preset')) {
|
|
70
|
+
// If presets array already exists but preset is missing
|
|
71
|
+
content = content.replace(/presets:\s*\[([\s\S]*?)\]/, (match, p1) => {
|
|
72
|
+
return `presets: [preset${p1.trim() ? ', ' + p1.trim() : ''}]`;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fs.writeFileSync(configPath, content, 'utf8');
|
|
77
|
+
|
|
78
|
+
// Copy index.css to src/ folder
|
|
79
|
+
const srcPath = path.resolve(process.cwd(), 'src');
|
|
80
|
+
if (!fs.existsSync(srcPath)) {
|
|
81
|
+
fs.mkdirSync(srcPath, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
const sourceCssPath = path.resolve(__dirname, '../index.css');
|
|
84
|
+
const targetCssPath = path.resolve(srcPath, 'index.css');
|
|
85
|
+
|
|
86
|
+
if (fs.existsSync(sourceCssPath)) {
|
|
87
|
+
fs.copyFileSync(sourceCssPath, targetCssPath);
|
|
88
|
+
console.log('✅ Copied index.css to src/index.css');
|
|
89
|
+
} else {
|
|
90
|
+
console.warn(`⚠️ Could not find index.css in ${PKG} package.`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log(`✅ ${PKG} initialized successfully!`);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('❌ Error modifying tailwind.config file or copying index.css:', error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
init();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes, RefObject, ReactNode, InputHTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
type PopoverPosition = `${'top' | 'bottom' | 'left' | 'right'}-${'start' | 'center' | 'end'}`;
|
|
5
|
+
type PopoverProps = HTMLAttributes<HTMLDivElement> & {
|
|
6
|
+
targetRef: RefObject<HTMLElement | null>;
|
|
7
|
+
position?: PopoverPosition;
|
|
8
|
+
open?: boolean;
|
|
9
|
+
onClose?: () => void;
|
|
10
|
+
onOpen?: (node: HTMLDivElement) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type InputProps = {
|
|
14
|
+
label?: string;
|
|
15
|
+
labelShrink?: boolean;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
startIcon?: ReactNode;
|
|
18
|
+
endIcon?: ReactNode;
|
|
19
|
+
bgColor?: string;
|
|
20
|
+
textColor?: string;
|
|
21
|
+
borderColor?: string;
|
|
22
|
+
primaryColor?: string;
|
|
23
|
+
fullWidth?: boolean;
|
|
24
|
+
inputContainerProps?: HTMLAttributes<HTMLDivElement>;
|
|
25
|
+
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'required'>;
|
|
26
|
+
declare const Input: react.ForwardRefExoticComponent<{
|
|
27
|
+
label?: string;
|
|
28
|
+
labelShrink?: boolean;
|
|
29
|
+
required?: boolean;
|
|
30
|
+
startIcon?: ReactNode;
|
|
31
|
+
endIcon?: ReactNode;
|
|
32
|
+
bgColor?: string;
|
|
33
|
+
textColor?: string;
|
|
34
|
+
borderColor?: string;
|
|
35
|
+
primaryColor?: string;
|
|
36
|
+
fullWidth?: boolean;
|
|
37
|
+
inputContainerProps?: HTMLAttributes<HTMLDivElement>;
|
|
38
|
+
} & Omit<InputHTMLAttributes<HTMLInputElement>, "required"> & react.RefAttributes<HTMLInputElement>>;
|
|
39
|
+
|
|
40
|
+
export { Input as I, type PopoverProps as P, type InputProps as a };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import react__default from 'react';
|
|
3
|
+
|
|
4
|
+
type Theme = "dark" | "light" | "system";
|
|
5
|
+
type ThemeProviderProps = {
|
|
6
|
+
children: react__default.ReactNode;
|
|
7
|
+
defaultTheme?: Theme;
|
|
8
|
+
storageKey?: string;
|
|
9
|
+
};
|
|
10
|
+
type ThemeProviderState = {
|
|
11
|
+
theme: Theme;
|
|
12
|
+
setTheme: (theme: Theme) => void;
|
|
13
|
+
};
|
|
14
|
+
declare function ThemeProvider({ children, defaultTheme, storageKey, ...props }: ThemeProviderProps): react_jsx_runtime.JSX.Element;
|
|
15
|
+
declare const useTheme: () => ThemeProviderState;
|
|
16
|
+
|
|
17
|
+
export { type Theme as T, ThemeProvider as a, type ThemeProviderProps as b, type ThemeProviderState as c, useTheme as u };
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import {
|
|
2
|
+
colorMix
|
|
3
|
+
} from "./chunk-MCQS3QNN.js";
|
|
4
|
+
|
|
5
|
+
// src/core/Popover/Popover.tsx
|
|
6
|
+
import { Fragment, useEffect, useLayoutEffect, useRef } from "react";
|
|
7
|
+
|
|
8
|
+
// src/core/Popover/Popover.utils.ts
|
|
9
|
+
function calculatePopoverStyle(container, defaultPosition) {
|
|
10
|
+
const rect = container.getBoundingClientRect();
|
|
11
|
+
const { top, left, bottom, right, width, height } = rect;
|
|
12
|
+
const [position, align = "center"] = defaultPosition.split("-");
|
|
13
|
+
const style = {};
|
|
14
|
+
let transform = "";
|
|
15
|
+
const round = (v) => Math.round(v);
|
|
16
|
+
if (["top", "bottom"].includes(position ?? "")) {
|
|
17
|
+
if (position === "top") style.bottom = `${round(window.innerHeight - top)}px`;
|
|
18
|
+
if (position === "bottom") style.top = `${round(bottom)}px`;
|
|
19
|
+
if (align === "start") style.left = `${round(left)}px`;
|
|
20
|
+
if (align === "end") style.right = `${round(window.innerWidth - right)}px`;
|
|
21
|
+
if (align === "center") {
|
|
22
|
+
style.left = `${round(left + width / 2)}px`;
|
|
23
|
+
transform += " translateX(-50%)";
|
|
24
|
+
style.transformOrigin = "0";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (["left", "right"].includes(position ?? "")) {
|
|
28
|
+
if (position === "left") style.right = `${round(window.innerWidth - left)}px`;
|
|
29
|
+
if (position === "right") style.left = `${round(right)}px`;
|
|
30
|
+
if (align === "start") style.top = `${round(top)}px`;
|
|
31
|
+
if (align === "end") style.bottom = `${round(window.innerHeight - bottom)}px`;
|
|
32
|
+
if (align === "center") {
|
|
33
|
+
style.top = `${round(top + height / 2)}px`;
|
|
34
|
+
transform += " translateY(-50%)";
|
|
35
|
+
style.transformOrigin = "50% 0";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
style.transform = transform.trim();
|
|
39
|
+
return style;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/core/Popover/Popover.tsx
|
|
43
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
44
|
+
function Popover(props) {
|
|
45
|
+
const {
|
|
46
|
+
targetRef,
|
|
47
|
+
children,
|
|
48
|
+
open,
|
|
49
|
+
onClose,
|
|
50
|
+
position: _position = "bottom-center",
|
|
51
|
+
onOpen,
|
|
52
|
+
...popoverProps
|
|
53
|
+
} = props;
|
|
54
|
+
const popoverRef = useRef(null);
|
|
55
|
+
const popoverPosition = useRef(_position);
|
|
56
|
+
function handlePopoverIntersect() {
|
|
57
|
+
if (!popoverRef.current) return;
|
|
58
|
+
const popoverRect = popoverRef.current.getBoundingClientRect();
|
|
59
|
+
if (popoverRect.top < 0) {
|
|
60
|
+
popoverPosition.current = popoverPosition.current.replace("top", "bottom");
|
|
61
|
+
} else if (popoverRect.bottom > window.innerHeight) {
|
|
62
|
+
popoverPosition.current = popoverPosition.current.replace("bottom", "top");
|
|
63
|
+
}
|
|
64
|
+
if (popoverRect.left < 0) {
|
|
65
|
+
popoverPosition.current = popoverPosition.current.replace("left", "right");
|
|
66
|
+
} else if (popoverRect.right > window.innerWidth) {
|
|
67
|
+
popoverPosition.current = popoverPosition.current.replace("right", "left");
|
|
68
|
+
}
|
|
69
|
+
handleResize();
|
|
70
|
+
}
|
|
71
|
+
function showPopover() {
|
|
72
|
+
if (!popoverRef.current) return;
|
|
73
|
+
popoverPosition.current = _position;
|
|
74
|
+
popoverRef.current.style.display = "flex";
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
if (!popoverRef.current) return;
|
|
77
|
+
popoverRef.current.style.opacity = "1";
|
|
78
|
+
popoverRef.current.style.scale = "1";
|
|
79
|
+
handlePopoverIntersect();
|
|
80
|
+
onOpen?.(popoverRef.current);
|
|
81
|
+
}, 10);
|
|
82
|
+
}
|
|
83
|
+
function hidePopover() {
|
|
84
|
+
if (!popoverRef.current) return;
|
|
85
|
+
popoverRef.current.style.opacity = "0";
|
|
86
|
+
popoverRef.current.style.scale = "0.8";
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
if (!popoverRef.current) return;
|
|
89
|
+
popoverRef.current.style.display = "none";
|
|
90
|
+
}, 200);
|
|
91
|
+
}
|
|
92
|
+
function handleClickOutside(event) {
|
|
93
|
+
if (event.target !== event.currentTarget) return;
|
|
94
|
+
onClose?.();
|
|
95
|
+
}
|
|
96
|
+
function handleResize() {
|
|
97
|
+
if (!popoverRef.current || !targetRef.current) return;
|
|
98
|
+
Object.assign(
|
|
99
|
+
popoverRef.current.style,
|
|
100
|
+
calculatePopoverStyle(targetRef.current, popoverPosition.current)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
useLayoutEffect(() => {
|
|
104
|
+
if (!popoverRef.current || !targetRef.current) return;
|
|
105
|
+
handleResize();
|
|
106
|
+
const resizeObserver = new ResizeObserver(handlePopoverIntersect);
|
|
107
|
+
resizeObserver.observe(popoverRef.current);
|
|
108
|
+
resizeObserver.observe(window.document.body);
|
|
109
|
+
window.addEventListener("scroll", handlePopoverIntersect, true);
|
|
110
|
+
return () => {
|
|
111
|
+
resizeObserver.disconnect();
|
|
112
|
+
window.removeEventListener("scroll", handlePopoverIntersect, true);
|
|
113
|
+
};
|
|
114
|
+
}, [targetRef?.current]);
|
|
115
|
+
useLayoutEffect(() => {
|
|
116
|
+
if (_position) popoverPosition.current = _position;
|
|
117
|
+
handleResize();
|
|
118
|
+
}, [_position]);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (open) showPopover();
|
|
121
|
+
else hidePopover();
|
|
122
|
+
}, [open]);
|
|
123
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
124
|
+
/* @__PURE__ */ jsx(
|
|
125
|
+
"div",
|
|
126
|
+
{
|
|
127
|
+
className: "fixed w-[100dvw] h-[100dvh] left-0 top-0",
|
|
128
|
+
style: { display: open ? "block" : "none" },
|
|
129
|
+
onClick: handleClickOutside
|
|
130
|
+
}
|
|
131
|
+
),
|
|
132
|
+
/* @__PURE__ */ jsx(
|
|
133
|
+
"div",
|
|
134
|
+
{
|
|
135
|
+
...popoverProps,
|
|
136
|
+
ref: popoverRef,
|
|
137
|
+
style: {
|
|
138
|
+
display: "flex",
|
|
139
|
+
position: "fixed",
|
|
140
|
+
transition: "all 200ms",
|
|
141
|
+
opacity: 0,
|
|
142
|
+
scale: 0.8,
|
|
143
|
+
...popoverProps.style
|
|
144
|
+
},
|
|
145
|
+
children
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
] });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/shared/utils/helper.ts
|
|
152
|
+
function cn(...classes) {
|
|
153
|
+
return classes.filter(Boolean).join(" ");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/core/Input/Input.tsx
|
|
157
|
+
import { forwardRef, useLayoutEffect as useLayoutEffect2, useRef as useRef2, useState } from "react";
|
|
158
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
159
|
+
var Input = forwardRef((props, ref) => {
|
|
160
|
+
const {
|
|
161
|
+
label,
|
|
162
|
+
startIcon,
|
|
163
|
+
endIcon,
|
|
164
|
+
required,
|
|
165
|
+
fullWidth = false,
|
|
166
|
+
labelShrink = false,
|
|
167
|
+
bgColor = "white",
|
|
168
|
+
textColor = "black",
|
|
169
|
+
borderColor = "black",
|
|
170
|
+
primaryColor = "royalblue",
|
|
171
|
+
inputContainerProps = {},
|
|
172
|
+
...inputProps
|
|
173
|
+
} = props;
|
|
174
|
+
const container = useRef2(null);
|
|
175
|
+
const [isFocus, setIsFocus] = useState(false);
|
|
176
|
+
function handleOnFocus(e) {
|
|
177
|
+
handleResize();
|
|
178
|
+
setIsFocus(true);
|
|
179
|
+
props.onFocus?.(e);
|
|
180
|
+
}
|
|
181
|
+
function handleOnBlur(e) {
|
|
182
|
+
handleResize();
|
|
183
|
+
setIsFocus(false);
|
|
184
|
+
props.onBlur?.(e);
|
|
185
|
+
}
|
|
186
|
+
function handleResize() {
|
|
187
|
+
if (!container.current) return;
|
|
188
|
+
const { offsetWidth: startIconWidth = 0 } = container.current.querySelector("#start-icon");
|
|
189
|
+
const { offsetWidth: endIconWidth = 0 } = container.current.querySelector("#end-icon");
|
|
190
|
+
const label2 = container.current.querySelector("#label");
|
|
191
|
+
const input = container.current.querySelector("input");
|
|
192
|
+
Object.assign(label2.style, document.activeElement === input || input.value || labelShrink === void 0 || labelShrink ? {
|
|
193
|
+
left: "2px",
|
|
194
|
+
backgroundColor: bgColor,
|
|
195
|
+
transform: "translateY(-22px) scale(0.75)"
|
|
196
|
+
} : {
|
|
197
|
+
left: `${startIconWidth + 8}px`,
|
|
198
|
+
backgroundColor: "transparent",
|
|
199
|
+
transform: "translateY(0) scale(1)"
|
|
200
|
+
});
|
|
201
|
+
Object.assign(input.style, {
|
|
202
|
+
paddingLeft: startIcon ? `${startIconWidth + 8}px` : "8px",
|
|
203
|
+
paddingRight: endIcon ? `${endIconWidth + 8}px` : "8px"
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
useLayoutEffect2(() => {
|
|
207
|
+
if (!container.current) return;
|
|
208
|
+
const resizeObserver = new ResizeObserver(handleResize);
|
|
209
|
+
resizeObserver.observe(container.current);
|
|
210
|
+
handleResize();
|
|
211
|
+
return () => {
|
|
212
|
+
resizeObserver.disconnect();
|
|
213
|
+
};
|
|
214
|
+
}, [container.current]);
|
|
215
|
+
return /* @__PURE__ */ jsxs2(
|
|
216
|
+
"div",
|
|
217
|
+
{
|
|
218
|
+
...inputContainerProps,
|
|
219
|
+
ref: container,
|
|
220
|
+
style: {
|
|
221
|
+
"--bg-color": bgColor,
|
|
222
|
+
"--text-color": textColor,
|
|
223
|
+
"--placeholder-color": colorMix(textColor, 80),
|
|
224
|
+
"--border-color": colorMix(borderColor, 50),
|
|
225
|
+
"--border-color-hover": colorMix(borderColor, 100),
|
|
226
|
+
"--light-primary-color": colorMix(primaryColor, 50),
|
|
227
|
+
"--primary-color": colorMix(primaryColor, 100),
|
|
228
|
+
width: fullWidth === void 0 || fullWidth ? "100%" : "auto",
|
|
229
|
+
...inputContainerProps?.style
|
|
230
|
+
},
|
|
231
|
+
className: cn(
|
|
232
|
+
"flex items-center gap-1 border rounded-md h-[44px] p-1 transition-all duration-200 relative bg-[var(--bg-color)]",
|
|
233
|
+
isFocus ? "ring-2 ring-[var(--light-primary-color)] border-[var(--primary-color)]" : "border-[var(--border-color)] hover:border-[var(--border-color-hover)]",
|
|
234
|
+
inputContainerProps?.className
|
|
235
|
+
),
|
|
236
|
+
children: [
|
|
237
|
+
/* @__PURE__ */ jsx2(
|
|
238
|
+
"div",
|
|
239
|
+
{
|
|
240
|
+
id: "label",
|
|
241
|
+
className: "absolute bg-[var(--bg-color)] text-[var(--text-color)] px-1 rounded-sm transition-all duration-200 select-none cursor-text",
|
|
242
|
+
onClick: () => container.current?.querySelector("input")?.focus(),
|
|
243
|
+
children: label
|
|
244
|
+
}
|
|
245
|
+
),
|
|
246
|
+
/* @__PURE__ */ jsx2("div", { id: "start-icon", className: "absolute left-0 h-full flex items-center justify-center", children: startIcon }),
|
|
247
|
+
/* @__PURE__ */ jsx2(
|
|
248
|
+
"input",
|
|
249
|
+
{
|
|
250
|
+
...inputProps,
|
|
251
|
+
value: inputProps.value,
|
|
252
|
+
ref,
|
|
253
|
+
required: required === void 0 || required,
|
|
254
|
+
className: cn(
|
|
255
|
+
"w-full h-full border-none outline-none rounded-sm transition-all duration-200 placeholder-[var(--placeholder-color)] text-[var(--text-color)] [&:-webkit-autofill]:[-webkit-text-fill-color:var(--text-color)] bg-[var(--bg-color)]",
|
|
256
|
+
isFocus || !label || labelShrink === void 0 || labelShrink ? "" : "placeholder:opacity-0",
|
|
257
|
+
inputProps.className
|
|
258
|
+
),
|
|
259
|
+
onFocus: handleOnFocus,
|
|
260
|
+
onBlur: handleOnBlur
|
|
261
|
+
}
|
|
262
|
+
),
|
|
263
|
+
/* @__PURE__ */ jsx2("div", { id: "end-icon", className: "absolute right-0 h-full flex items-center justify-center", children: endIcon })
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// src/core/ShowWithAnimation/index.tsx
|
|
270
|
+
import { useLayoutEffect as useLayoutEffect3, useRef as useRef3, useState as useState2 } from "react";
|
|
271
|
+
|
|
272
|
+
// src/core/ShowWithAnimation/const.ts
|
|
273
|
+
var defaultAnimationStyle = {
|
|
274
|
+
fade: {
|
|
275
|
+
children: {
|
|
276
|
+
from: {
|
|
277
|
+
opacity: "0",
|
|
278
|
+
scale: "0.95"
|
|
279
|
+
},
|
|
280
|
+
active: {
|
|
281
|
+
opacity: "1",
|
|
282
|
+
scale: "1"
|
|
283
|
+
},
|
|
284
|
+
to: {
|
|
285
|
+
opacity: "0",
|
|
286
|
+
scale: "1.05"
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
slide: {
|
|
291
|
+
children: {
|
|
292
|
+
from: {
|
|
293
|
+
opacity: "0",
|
|
294
|
+
scale: "0.95",
|
|
295
|
+
transform: "translateY(20px)"
|
|
296
|
+
},
|
|
297
|
+
active: {
|
|
298
|
+
opacity: "1",
|
|
299
|
+
scale: "1",
|
|
300
|
+
transform: "translateY(0px)"
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// src/core/ShowWithAnimation/index.tsx
|
|
307
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
308
|
+
function ShowWithAnimation(props) {
|
|
309
|
+
const {
|
|
310
|
+
when,
|
|
311
|
+
children,
|
|
312
|
+
containerProps,
|
|
313
|
+
animationStyle,
|
|
314
|
+
otherwise = null,
|
|
315
|
+
removeOnHide = false,
|
|
316
|
+
animationType = "fade",
|
|
317
|
+
animationDuration = 300,
|
|
318
|
+
...wrapperProps
|
|
319
|
+
} = props;
|
|
320
|
+
if (animationStyle) {
|
|
321
|
+
if (!animationStyle.children.to) {
|
|
322
|
+
animationStyle.children.to = animationStyle.children.from;
|
|
323
|
+
}
|
|
324
|
+
if (!animationStyle.otherwise) {
|
|
325
|
+
animationStyle.otherwise = animationStyle.children;
|
|
326
|
+
}
|
|
327
|
+
if (!animationStyle.otherwise.to) {
|
|
328
|
+
animationStyle.otherwise.to = animationStyle.otherwise.from;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const defaultAnimationStyle2 = (() => {
|
|
332
|
+
const animationStyle2 = defaultAnimationStyle[animationType];
|
|
333
|
+
if (!animationStyle2.children.to) {
|
|
334
|
+
animationStyle2.children.to = animationStyle2.children.from;
|
|
335
|
+
}
|
|
336
|
+
if (!animationStyle2.otherwise) {
|
|
337
|
+
animationStyle2.otherwise = animationStyle2.children;
|
|
338
|
+
}
|
|
339
|
+
if (!animationStyle2.otherwise.to) {
|
|
340
|
+
animationStyle2.otherwise.to = animationStyle2.otherwise.from;
|
|
341
|
+
}
|
|
342
|
+
return animationStyle2;
|
|
343
|
+
})();
|
|
344
|
+
const childrenContainer = useRef3(null);
|
|
345
|
+
const otherwiseContainer = useRef3(null);
|
|
346
|
+
const [isChildrenVisible, setIsChildrenVisible] = useState2(when);
|
|
347
|
+
const [isOtherwiseVisible, setIsOtherwiseVisible] = useState2(!when);
|
|
348
|
+
function handleShow() {
|
|
349
|
+
setIsChildrenVisible(true);
|
|
350
|
+
setTimeout(() => {
|
|
351
|
+
if (!childrenContainer.current) return;
|
|
352
|
+
Object.assign(childrenContainer.current.style, {
|
|
353
|
+
visibility: "visible",
|
|
354
|
+
...defaultAnimationStyle2?.children.active,
|
|
355
|
+
...animationStyle?.children.active,
|
|
356
|
+
transitionDuration: `${animationDuration}ms`
|
|
357
|
+
});
|
|
358
|
+
}, 10);
|
|
359
|
+
if (!otherwiseContainer.current) return;
|
|
360
|
+
Object.assign(otherwiseContainer.current.style, {
|
|
361
|
+
visibility: "hidden",
|
|
362
|
+
...defaultAnimationStyle2?.otherwise?.to,
|
|
363
|
+
...animationStyle?.otherwise?.to,
|
|
364
|
+
transitionDuration: `${animationDuration}ms`
|
|
365
|
+
});
|
|
366
|
+
otherwiseContainer.current.addEventListener("transitionend", handleOtherwiseTransitionEnd, { once: true });
|
|
367
|
+
}
|
|
368
|
+
function handleHide() {
|
|
369
|
+
setIsOtherwiseVisible(true);
|
|
370
|
+
setTimeout(() => {
|
|
371
|
+
if (!otherwiseContainer.current) return;
|
|
372
|
+
Object.assign(otherwiseContainer.current.style, {
|
|
373
|
+
visibility: "visible",
|
|
374
|
+
...defaultAnimationStyle2?.otherwise?.active,
|
|
375
|
+
...animationStyle?.otherwise?.active,
|
|
376
|
+
transitionDuration: `${animationDuration}ms`
|
|
377
|
+
});
|
|
378
|
+
}, 10);
|
|
379
|
+
if (!childrenContainer.current) return;
|
|
380
|
+
Object.assign(childrenContainer.current.style, {
|
|
381
|
+
visibility: "hidden",
|
|
382
|
+
...defaultAnimationStyle2?.children.to,
|
|
383
|
+
...animationStyle?.children.to,
|
|
384
|
+
transitionDuration: `${animationDuration}ms`
|
|
385
|
+
});
|
|
386
|
+
childrenContainer.current.addEventListener("transitionend", handleChildrenTransitionEnd, { once: true });
|
|
387
|
+
}
|
|
388
|
+
function handleChildrenTransitionEnd() {
|
|
389
|
+
if (!childrenContainer.current) return;
|
|
390
|
+
if (childrenContainer.current?.style.visibility === "visible") return;
|
|
391
|
+
if (removeOnHide) return setIsChildrenVisible(false);
|
|
392
|
+
Object.assign(childrenContainer.current.style, {
|
|
393
|
+
...defaultAnimationStyle2?.children.from,
|
|
394
|
+
...animationStyle?.children.from,
|
|
395
|
+
transitionDuration: "0ms"
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
function handleOtherwiseTransitionEnd() {
|
|
399
|
+
if (!otherwiseContainer.current) return;
|
|
400
|
+
if (otherwiseContainer.current?.style.visibility === "visible") return;
|
|
401
|
+
if (removeOnHide) return setIsOtherwiseVisible(false);
|
|
402
|
+
Object.assign(otherwiseContainer.current.style, {
|
|
403
|
+
...defaultAnimationStyle2?.otherwise?.from,
|
|
404
|
+
...animationStyle?.otherwise?.from,
|
|
405
|
+
transitionDuration: "0ms"
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
useLayoutEffect3(() => {
|
|
409
|
+
when ? handleShow() : handleHide();
|
|
410
|
+
}, [when]);
|
|
411
|
+
return removeOnHide && !isChildrenVisible && otherwise === null ? null : /* @__PURE__ */ jsxs3(
|
|
412
|
+
"div",
|
|
413
|
+
{
|
|
414
|
+
...containerProps,
|
|
415
|
+
className: cn("relative flex items-center justify-center", containerProps?.className),
|
|
416
|
+
style: { ...containerProps?.style, position: "relative" },
|
|
417
|
+
children: [
|
|
418
|
+
isChildrenVisible ? /* @__PURE__ */ jsx3(
|
|
419
|
+
"div",
|
|
420
|
+
{
|
|
421
|
+
...wrapperProps,
|
|
422
|
+
ref: childrenContainer,
|
|
423
|
+
id: "children-container",
|
|
424
|
+
style: {
|
|
425
|
+
...defaultAnimationStyle2?.children.from,
|
|
426
|
+
...animationStyle?.children.from,
|
|
427
|
+
...wrapperProps.style,
|
|
428
|
+
position: when ? "relative" : otherwise ? "absolute" : "relative"
|
|
429
|
+
},
|
|
430
|
+
children
|
|
431
|
+
}
|
|
432
|
+
) : null,
|
|
433
|
+
otherwise && isOtherwiseVisible ? /* @__PURE__ */ jsx3(
|
|
434
|
+
"div",
|
|
435
|
+
{
|
|
436
|
+
...wrapperProps,
|
|
437
|
+
ref: otherwiseContainer,
|
|
438
|
+
id: "otherwise-container",
|
|
439
|
+
style: {
|
|
440
|
+
...defaultAnimationStyle2?.otherwise?.from,
|
|
441
|
+
...animationStyle?.otherwise?.from,
|
|
442
|
+
transitionDuration: `all ${animationDuration}ms`,
|
|
443
|
+
...wrapperProps.style,
|
|
444
|
+
position: when ? "absolute" : "relative"
|
|
445
|
+
},
|
|
446
|
+
children: otherwise
|
|
447
|
+
}
|
|
448
|
+
) : null
|
|
449
|
+
]
|
|
450
|
+
}
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// src/core/Show/index.tsx
|
|
455
|
+
function Show({ when, children, otherwise = null }) {
|
|
456
|
+
return when ? children : otherwise;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export {
|
|
460
|
+
Popover,
|
|
461
|
+
cn,
|
|
462
|
+
Input,
|
|
463
|
+
ShowWithAnimation,
|
|
464
|
+
Show
|
|
465
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/hooks/useKeyboardShortcuts.ts
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
function useKeyboardShortcuts(shortcuts) {
|
|
4
|
+
function handleKeyDown(event) {
|
|
5
|
+
for (const shortcut of shortcuts) {
|
|
6
|
+
const { key, callback, options = {} } = shortcut;
|
|
7
|
+
if ([
|
|
8
|
+
event.key.toLowerCase() === key.toLowerCase(),
|
|
9
|
+
!!options.ctrl === event.ctrlKey,
|
|
10
|
+
!!options.shift === event.shiftKey,
|
|
11
|
+
!!options.alt === event.altKey,
|
|
12
|
+
!!options.meta === event.metaKey,
|
|
13
|
+
options.customKeyCheck ? options.customKeyCheck(event) : true
|
|
14
|
+
].some((con) => con === false)) continue;
|
|
15
|
+
if (options.preventDefault) {
|
|
16
|
+
event.preventDefault();
|
|
17
|
+
}
|
|
18
|
+
callback(event);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
23
|
+
return () => {
|
|
24
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
25
|
+
};
|
|
26
|
+
}, [shortcuts]);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/hooks/useTabNavigation.ts
|
|
30
|
+
import { useLayoutEffect, useSyncExternalStore } from "react";
|
|
31
|
+
function useTabNavigation(options = {}) {
|
|
32
|
+
const {
|
|
33
|
+
defaultTab: _defaultTab,
|
|
34
|
+
tabs = [],
|
|
35
|
+
urlKey = "tab",
|
|
36
|
+
replaceTab = false
|
|
37
|
+
} = options;
|
|
38
|
+
const defaultTab = _defaultTab ?? tabs[0];
|
|
39
|
+
const tab = useSyncExternalStore(subscribeToTabChange, getCurrentTab);
|
|
40
|
+
function subscribeToTabChange(callback) {
|
|
41
|
+
window.addEventListener("popstate", callback);
|
|
42
|
+
return () => {
|
|
43
|
+
window.removeEventListener("popstate", callback);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function getCurrentTab() {
|
|
47
|
+
const params = new URLSearchParams(window.location.search);
|
|
48
|
+
if (!params.has(urlKey)) return defaultTab;
|
|
49
|
+
return params.get(urlKey);
|
|
50
|
+
}
|
|
51
|
+
function setTab(newTab, replace = replaceTab) {
|
|
52
|
+
if (tab.length && !tabs.includes(newTab)) {
|
|
53
|
+
console.warn(`Invalid tab: ${newTab}. Valid tabs are: ${tabs.join(", ")}`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const params = new URLSearchParams(window.location.search);
|
|
57
|
+
params.set(urlKey, newTab);
|
|
58
|
+
const newUrl = `${window.location.pathname}?${params.toString()}`;
|
|
59
|
+
if (replace) {
|
|
60
|
+
window.history.replaceState({}, "", newUrl);
|
|
61
|
+
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
62
|
+
} else {
|
|
63
|
+
window.history.pushState({}, "", newUrl);
|
|
64
|
+
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
useLayoutEffect(() => {
|
|
68
|
+
setTab(tab, true);
|
|
69
|
+
}, []);
|
|
70
|
+
return { tab, setTab };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export {
|
|
74
|
+
useKeyboardShortcuts,
|
|
75
|
+
useTabNavigation
|
|
76
|
+
};
|