@zuzjs/ui 0.9.5 → 0.9.7

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 (70) hide show
  1. package/dist/cjs/comps/Chart/index.d.ts +8 -0
  2. package/dist/cjs/comps/Chart/index.js +66 -0
  3. package/dist/cjs/comps/Chart/types.d.ts +10 -0
  4. package/dist/cjs/comps/Chart/types.js +4 -0
  5. package/dist/cjs/comps/Cover/index.d.ts +3 -1
  6. package/dist/cjs/comps/Cover/index.js +3 -3
  7. package/dist/cjs/comps/Form/index.js +7 -3
  8. package/dist/cjs/comps/Search/index.d.ts +4 -0
  9. package/dist/cjs/comps/Search/index.js +7 -3
  10. package/dist/cjs/comps/Search/types.d.ts +5 -0
  11. package/dist/cjs/comps/Select/index.d.ts +3 -0
  12. package/dist/cjs/comps/Select/index.js +13 -5
  13. package/dist/cjs/comps/Select/types.d.ts +7 -1
  14. package/dist/cjs/comps/Spinner/index.d.ts +3 -1
  15. package/dist/cjs/comps/Spinner/index.js +11 -4
  16. package/dist/cjs/comps/TextWheel/index.d.ts +2 -0
  17. package/dist/cjs/comps/TextWheel/index.js +24 -12
  18. package/dist/cjs/comps/TextWheel/types.d.ts +2 -0
  19. package/dist/cjs/comps/index.d.ts +2 -0
  20. package/dist/cjs/comps/index.js +2 -0
  21. package/dist/cjs/funs/stylesheet.js +3 -1
  22. package/dist/cjs/hooks/index.d.ts +2 -0
  23. package/dist/cjs/hooks/index.js +2 -0
  24. package/dist/cjs/hooks/useLineChart.d.ts +25 -0
  25. package/dist/cjs/hooks/useLineChart.js +86 -0
  26. package/dist/cjs/hooks/usePosition.d.ts +12 -0
  27. package/dist/cjs/hooks/usePosition.js +191 -0
  28. package/dist/cjs/hooks/useResizeObserver.d.ts +1 -1
  29. package/dist/cjs/hooks/useResizeObserver.js +5 -4
  30. package/dist/cjs/hooks/useScrollbar.js +115 -70
  31. package/dist/cjs/types/enums.d.ts +2 -0
  32. package/dist/cjs/types/enums.js +2 -0
  33. package/dist/cjs/types/index.d.ts +2 -1
  34. package/dist/css/styles.css +1 -1
  35. package/dist/esm/comps/Chart/index.d.ts +8 -0
  36. package/dist/esm/comps/Chart/index.js +66 -0
  37. package/dist/esm/comps/Chart/types.d.ts +10 -0
  38. package/dist/esm/comps/Chart/types.js +4 -0
  39. package/dist/esm/comps/Cover/index.d.ts +3 -1
  40. package/dist/esm/comps/Cover/index.js +3 -3
  41. package/dist/esm/comps/Form/index.js +7 -3
  42. package/dist/esm/comps/Search/index.d.ts +4 -0
  43. package/dist/esm/comps/Search/index.js +7 -3
  44. package/dist/esm/comps/Search/types.d.ts +5 -0
  45. package/dist/esm/comps/Select/index.d.ts +3 -0
  46. package/dist/esm/comps/Select/index.js +13 -5
  47. package/dist/esm/comps/Select/types.d.ts +7 -1
  48. package/dist/esm/comps/Spinner/index.d.ts +3 -1
  49. package/dist/esm/comps/Spinner/index.js +11 -4
  50. package/dist/esm/comps/TextWheel/index.d.ts +2 -0
  51. package/dist/esm/comps/TextWheel/index.js +24 -12
  52. package/dist/esm/comps/TextWheel/types.d.ts +2 -0
  53. package/dist/esm/comps/index.d.ts +2 -0
  54. package/dist/esm/comps/index.js +2 -0
  55. package/dist/esm/funs/stylesheet.js +3 -1
  56. package/dist/esm/hooks/index.d.ts +2 -0
  57. package/dist/esm/hooks/index.js +2 -0
  58. package/dist/esm/hooks/useLineChart.d.ts +25 -0
  59. package/dist/esm/hooks/useLineChart.js +86 -0
  60. package/dist/esm/hooks/usePosition.d.ts +12 -0
  61. package/dist/esm/hooks/usePosition.js +191 -0
  62. package/dist/esm/hooks/useResizeObserver.d.ts +1 -1
  63. package/dist/esm/hooks/useResizeObserver.js +5 -4
  64. package/dist/esm/hooks/useScrollbar.js +115 -70
  65. package/dist/esm/types/enums.d.ts +2 -0
  66. package/dist/esm/types/enums.js +2 -0
  67. package/dist/esm/types/index.d.ts +2 -1
  68. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  69. package/dist/tsconfig.tsbuildinfo +1 -1
  70. package/package.json +2 -2
@@ -1,15 +1,17 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { forwardRef, useEffect, useId, useMemo, useRef, useState } from "react";
4
- import { useBase } from "../../hooks";
4
+ import { useBase, usePosition } from "../../hooks";
5
+ import { Position } from "../../types/enums";
5
6
  import Box from "../Box";
6
7
  import Button from "../Button";
8
+ import Icon from "../Icon";
7
9
  import Input from "../Input";
8
10
  import SVGIcons from "../svgicons";
9
11
  import Text from "../Text";
10
12
  import OptionItem from "./optionItem";
11
13
  const Select = forwardRef((props, ref) => {
12
- const { selected, options, label, name, variant, search: withSearch, searchPlaceholder, onChange, ...pops } = props;
14
+ const { selected, options, label, name, variant, search: withSearch, searchPlaceholder, maxHeight, arrowDownIcon = SVGIcons.arrowDown, arrowUpIcon = SVGIcons.arrowUp, onChange, ...pops } = props;
13
15
  const [value, setValue] = useState(selected ?
14
16
  typeof selected === `string` ? options.find(fo => fo.value === selected) : selected
15
17
  : options[0]);
@@ -17,8 +19,10 @@ const Select = forwardRef((props, ref) => {
17
19
  const [query, setQuery] = useState(null);
18
20
  const _ref = useRef(null);
19
21
  const _search = useRef(null);
22
+ const _pop = useRef(null);
20
23
  const _did = useId();
21
24
  const _id = useMemo(() => name || _did, []);
25
+ const { reposition } = usePosition(_pop, { direction: Position.Bottom, offset: 2 });
22
26
  const { className, style, rest } = useBase(pops);
23
27
  const updateValue = (o) => {
24
28
  setValue(o);
@@ -28,6 +32,7 @@ const Select = forwardRef((props, ref) => {
28
32
  document.body.addEventListener(`click`, (e) => {
29
33
  setChoosing(false);
30
34
  });
35
+ window.dispatchEvent(new Event('resize'));
31
36
  }, []);
32
37
  useEffect(() => {
33
38
  if (choosing) {
@@ -39,10 +44,13 @@ const Select = forwardRef((props, ref) => {
39
44
  }
40
45
  setQuery(null);
41
46
  }
47
+ reposition();
42
48
  }, [choosing]);
43
- return _jsxs(Box, { className: `--select ${variant ? `--${variant}` : ``} ${name ? `--${name}` : ``} rel`.trim(), name: _id, children: [_jsxs(Button, { "data-value": value ? `string` == typeof value ? value : value.value : value || `-1`, className: `--selected flex aic rel ${className}`.trim(), withLabel: false, style: style, onClick: (e) => setChoosing(prev => !prev), ...rest, children: [_jsx(Text, { className: `--label`, children: value ? `string` == typeof value ? value : value.label : label || `Choose` }), _jsx(Box, { className: `--svg-arrow rel flex aic jcc`, children: choosing ? SVGIcons.arrowUp : SVGIcons.arrowDown })] }), _jsxs(Box, { id: _id, className: `--options-list flex cols abs`, style: {
44
- pointerEvents: choosing ? `auto` : `none`,
45
- }, animate: {
49
+ return _jsxs(Box, { className: `--select ${variant ? `--${variant}` : ``} ${name ? `--${name}` : ``} rel`.trim(), name: _id, children: [_jsxs(Button, { "data-value": value ? `string` == typeof value ? value : value.value : value || `-1`, className: `--selected flex aic rel ${className}`.trim(), withLabel: false, style: style, onClick: (e) => setChoosing(prev => !prev), ...rest, children: [_jsx(Text, { className: `--label`, children: value ? `string` == typeof value ? value : value.label : label || `Choose` }), _jsx(Box, { className: `--svg-arrow rel flex aic jcc`, children: choosing ?
50
+ `string` === typeof arrowUpIcon ? _jsx(Icon, { name: arrowUpIcon, as: `--search-action` }) : arrowUpIcon :
51
+ `string` === typeof arrowDownIcon ? _jsx(Icon, { name: arrowDownIcon, as: `--search-action` }) : arrowDownIcon })] }), _jsxs(Box, { id: _id, className: `--options-list flex cols abs`, "aria-hidden": !choosing, style: {
52
+ maxHeight: maxHeight || `auto`
53
+ }, ref: _pop, fx: {
46
54
  from: { y: 5, opacity: 0 },
47
55
  to: { y: 0, opacity: 1 },
48
56
  when: choosing,
@@ -1,4 +1,4 @@
1
- import { FormEventHandler } from "react";
1
+ import { FormEventHandler, ReactNode } from "react";
2
2
  import { FORMVALIDATION, Variant } from "../../types/enums";
3
3
  import { BoxProps } from "../Box";
4
4
  /**
@@ -55,4 +55,10 @@ export type SelectProps = Omit<BoxProps, "onChange"> & {
55
55
  * Placeholder text for the search input field.
56
56
  */
57
57
  searchPlaceholder?: string;
58
+ /**
59
+ * Max Height
60
+ */
61
+ maxHeight?: number;
62
+ arrowDownIcon?: string | ReactNode;
63
+ arrowUpIcon?: string | ReactNode;
58
64
  };
@@ -1,8 +1,9 @@
1
- import { Size, SPINNER } from "../../types/enums";
1
+ import { Size, SPINNER, Variant } from "../../types/enums";
2
2
  import { BoxProps } from "../Box";
3
3
  export type SpinnerProps = BoxProps & {
4
4
  type?: SPINNER;
5
5
  size?: Size | number;
6
+ variant?: Variant | number;
6
7
  width?: number;
7
8
  color?: string;
8
9
  background?: string;
@@ -12,6 +13,7 @@ export type SpinnerProps = BoxProps & {
12
13
  declare const Spinner: import("react").ForwardRefExoticComponent<BoxProps & {
13
14
  type?: SPINNER;
14
15
  size?: Size | number;
16
+ variant?: Variant | number;
15
17
  width?: number;
16
18
  color?: string;
17
19
  background?: string;
@@ -2,10 +2,10 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { forwardRef } from "react";
3
3
  import { hexToRgba } from "../../funs";
4
4
  import { useBase } from "../../hooks";
5
- import { Size, SPINNER } from "../../types/enums";
5
+ import { Size, SPINNER, Variant } from "../../types/enums";
6
6
  import Box from "../Box";
7
7
  const Spinner = forwardRef((props, ref) => {
8
- const { type, size, width, speed, color, background, foreground, ...pops } = props;
8
+ const { type, size, variant, width, speed, color, background, foreground, ...pops } = props;
9
9
  const defaultColor = `#000000`;
10
10
  const { className, style, rest } = useBase(pops);
11
11
  const build = () => {
@@ -18,13 +18,20 @@ const Spinner = forwardRef((props, ref) => {
18
18
  default: 20
19
19
  };
20
20
  const _size = size ? Object.values(Size).includes(size) ? sizes[size] : size : sizes.default;
21
+ const variants = {
22
+ [Variant.Small]: 20,
23
+ [Variant.Medium]: 30,
24
+ [Variant.Large]: 50,
25
+ default: 20
26
+ };
27
+ const _variant = variant ? Object.values(Variant).includes(variant) ? variants[variant] : variant : variants.default;
21
28
  const _animationSetting = {
22
29
  animationDuration: `${speed || .6}s`,
23
30
  animationTimingFunction: `linear`
24
31
  };
25
32
  const _props = (type || SPINNER.Simple) == SPINNER.Simple ? {
26
- width: _size,
27
- height: _size,
33
+ width: _variant || _size,
34
+ height: _variant || _size,
28
35
  border: `${width || 3}px solid ${bg}`,
29
36
  borderRadius: `50%`,
30
37
  borderTopColor: c,
@@ -4,5 +4,7 @@ declare const TextWheel: React.ForwardRefExoticComponent<Omit<import("..").BoxPr
4
4
  value?: number | string;
5
5
  color?: string;
6
6
  direction?: `up` | `down`;
7
+ charDelay?: number | ((index: number) => number);
8
+ charDuration?: number | ((index: number) => number);
7
9
  } & React.RefAttributes<TextWheelHandler>>;
8
10
  export default TextWheel;
@@ -1,54 +1,66 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
4
- import Box from '../Box';
5
- import Span from '../Span';
4
+ import Box from '../Box'; // Assuming these are your custom components
5
+ import Span from '../Span'; // Assuming these are your custom components
6
6
  const TextWheel = forwardRef((props, ref) => {
7
7
  const { as, value, color, direction, ...rest } = props;
8
8
  const divRef = useRef(null);
9
+ // Internal state to manage the displayed value
9
10
  const [_value, _setValue] = useState(value || 0);
11
+ // Expose methods to parent components via ref
10
12
  useImperativeHandle(ref, () => ({
13
+ // Updates the value without immediately triggering a full re-render of characters
14
+ // Useful for when the number of digits changes.
11
15
  updateValue(v) {
12
- if (_value.toString().length != v.toString().length) {
16
+ if (_value.toString().length !== v.toString().length) {
13
17
  _setValue(v);
14
18
  }
15
19
  },
20
+ // Sets the value and triggers the animation for each character
16
21
  setValue(v) {
17
- this.updateValue(v);
22
+ this.updateValue(v); // First, update the internal value to handle length changes
18
23
  if (divRef.current) {
19
24
  const chars = v.toString().split('');
20
25
  divRef.current.querySelectorAll('.--wheel-char').forEach((charElement, index) => {
21
26
  const char = chars[index];
22
27
  if (charElement instanceof HTMLElement) {
28
+ // Set the data-value attribute, which CSS can then use
23
29
  charElement.setAttribute('data-value', char);
24
30
  const track = charElement.querySelector('.--wheel-char-track');
25
31
  if (track instanceof HTMLElement) {
32
+ // Directly update the CSS custom property for immediate animation
26
33
  track.style.setProperty('--v', char);
34
+ // Force a reflow to ensure the transition plays even if the value is the same
35
+ // This is a common trick for re-triggering CSS animations/transitions
36
+ void track.offsetWidth;
27
37
  }
28
38
  }
29
39
  });
30
40
  }
31
41
  }
32
42
  }));
43
+ // Effect to update internal value when the prop `value` changes
33
44
  useEffect(() => {
34
- // console.log(value)
35
45
  _setValue(value || 0);
36
46
  }, [value]);
37
- return _jsxs(Box, { className: `--text-wheel flex aic rel`, "aria-hidden": true, as: as, ref: divRef, ...rest, children: [(_value || 0).toString().split('').map((char, index) => {
47
+ return (_jsxs(Box, { className: `--text-wheel flex aic rel`, "aria-hidden": true, as: as, ref: divRef, ...rest, children: [(_value || 0).toString().split('').map((char, index) => {
48
+ // Render non-numeric characters directly (e.g., decimal points)
38
49
  if (isNaN(parseInt(char, 10))) {
39
- return _jsx(Span, { className: "--wheel-char wheel-char-symbol grid", children: char }, `wheel-char-${index}`);
50
+ return _jsx(Span, { className: "--wheel-char wheel-char-symbol grid", children: char }, `wheel-char-symbol-${index}`);
40
51
  }
41
- return _jsx(Span, { "data-value": char, className: `--wheel-char grid ${index > char.toString().split('').length - 3 ? '--wheel-fraction' : ''}`.trim(), children: _jsxs(Span, { className: `--wheel-char-track --wheel-track-${direction || `down`} grid`, style: {
42
- '--v': char
43
- }, children: [_jsx(Span, { children: !direction || direction == `down` ? 0 : 9 }), (!direction || direction == `down` ?
52
+ // Render numeric characters with the wheel effect
53
+ return (_jsx(Span, { "data-value": char, className: `--wheel-char grid ${index > (_value || 0).toString().length - 3 ? '--wheel-fraction' : ''}`.trim(), children: _jsxs(Span, { className: `--wheel-char-track --wheel-track-${direction || `down`} grid`, style: {
54
+ '--v': char // Set the custom property for the current character value
55
+ }, children: [_jsx(Span, { children: !direction || direction === `down` ? 0 : 9 }), (!direction || direction === `down` ?
44
56
  [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
45
57
  : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).map((val, indx) => {
46
58
  return _jsx(Span, { children: val }, `${index}--${indx}`);
47
- }), _jsx(Span, { children: !direction || direction == `down` ? 9 : 0 })] }) }, `wheel-char-${index}`);
59
+ }), _jsx(Span, { children: !direction || direction === `down` ? 9 : 0 })] }) }, `wheel-char-${index}`));
48
60
  }), color && _jsx(Box, { className: `abs fillx`, style: {
49
61
  zIndex: 1,
50
62
  background: `linear-gradient(0deg, ${color}, transparent, transparent, transparent, ${color})`,
51
- } })] });
63
+ } })] }));
52
64
  });
53
65
  TextWheel.displayName = `Zuz.TextWheel`;
54
66
  export default TextWheel;
@@ -3,6 +3,8 @@ export type TextWheelProps = Omit<BoxProps, "name"> & {
3
3
  value?: number | string;
4
4
  color?: string;
5
5
  direction?: `up` | `down`;
6
+ charDelay?: number | ((index: number) => number);
7
+ charDuration?: number | ((index: number) => number);
6
8
  };
7
9
  export interface TextWheelHandler {
8
10
  setValue: (v: number | string) => void;
@@ -12,6 +12,8 @@ export * from './Avatar/types';
12
12
  export { default as Box, type BoxProps } from './Box';
13
13
  export { default as Button } from './Button';
14
14
  export * from './Button/types';
15
+ export { default as Chart } from './Chart';
16
+ export * from './Chart/types';
15
17
  export { default as CheckBox } from './CheckBox';
16
18
  export * from './CheckBox/types';
17
19
  export { default as ColorScheme } from './ColorScheme';
@@ -12,6 +12,8 @@ export * from './Avatar/types';
12
12
  export { default as Box } from './Box';
13
13
  export { default as Button } from './Button';
14
14
  export * from './Button/types';
15
+ export { default as Chart } from './Chart';
16
+ export * from './Chart/types';
15
17
  export { default as CheckBox } from './CheckBox';
16
18
  export * from './CheckBox/types';
17
19
  export { default as ColorScheme } from './ColorScheme';
@@ -308,11 +308,13 @@ export const cssDirect = {
308
308
  "translate": "transform: translate(__VALUE__);",
309
309
  "translateX": "transform: translateX(__VALUE__);",
310
310
  "translateY": "transform: translateY(__VALUE__);",
311
+ "translateZ": "transform: translateZ(__VALUE__);",
311
312
  "x": "transform: translateX(__VALUE__);",
312
313
  "y": "transform: translateY(__VALUE__);",
314
+ "z": "transform: translateZ(__VALUE__);",
313
315
  "rotate": "transform: rotate(__VALUE__);",
314
316
  "rotateX": "transform: rotateX(__VALUE__);",
315
- "rotateY": "transform: rotatY(__VALUE__);",
317
+ "rotateY": "transform: rotateY(__VALUE__);",
316
318
  "rotateZ": "transform: rotateZ(__VALUE__);",
317
319
  "rotate3d": "transform: rotate3d(__X__, __Y__, __Z__, __A__);",
318
320
  "scale": "transform: scale(__VALUE__);",
@@ -11,6 +11,7 @@ export { default as useDebounce } from './useDebounce';
11
11
  export { default as useDelayed, default as useMounted } from './useDelayed';
12
12
  export { default as useDevice } from './useDevice';
13
13
  export { default as useDimensions } from './useDimensions';
14
+ export { default as useLineChart, type DataPoint, type LineChartProps, type UseLineChartDimensions, type UseLineChartReturn } from './useLineChart';
14
15
  export { default as useMediaPlayer, type MediaPlayerProps, type MediaType } from './useMediaPlayer';
15
16
  export { default as useMergedRefs } from './useMergedRefs';
16
17
  export { default as useMutationObserver, type MutationCallback } from './useMutationObserver';
@@ -26,6 +27,7 @@ export { default as useScrollPhysics } from './useScrollPhysics';
26
27
  export { default as useSheet } from './useSheet';
27
28
  export { default as useShortcuts } from './useShortcuts';
28
29
  export { default as useNetworkStatus } from './useNetworkStatus';
30
+ export { default as usePosition } from './usePosition';
29
31
  export { default as useResizeObserver } from './useResizeObserver';
30
32
  export { default as useSlider } from './useSlider';
31
33
  export { default as useToast } from './useToast';
@@ -11,6 +11,7 @@ export { default as useDebounce } from './useDebounce';
11
11
  export { default as useDelayed, default as useMounted } from './useDelayed';
12
12
  export { default as useDevice } from './useDevice';
13
13
  export { default as useDimensions } from './useDimensions';
14
+ export { default as useLineChart } from './useLineChart';
14
15
  export { default as useMediaPlayer } from './useMediaPlayer';
15
16
  export { default as useMergedRefs } from './useMergedRefs';
16
17
  export { default as useMutationObserver } from './useMutationObserver';
@@ -27,6 +28,7 @@ export { default as useScrollPhysics } from './useScrollPhysics';
27
28
  export { default as useSheet } from './useSheet';
28
29
  export { default as useShortcuts } from './useShortcuts';
29
30
  export { default as useNetworkStatus } from './useNetworkStatus';
31
+ export { default as usePosition } from './usePosition';
30
32
  export { default as useResizeObserver } from './useResizeObserver';
31
33
  export { default as useSlider } from './useSlider';
32
34
  export { default as useToast } from './useToast';
@@ -0,0 +1,25 @@
1
+ export interface DataPoint {
2
+ x: number;
3
+ y: number;
4
+ }
5
+ export interface UseLineChartDimensions {
6
+ width: number;
7
+ height: number;
8
+ }
9
+ export interface UseLineChartReturn {
10
+ pathD: string;
11
+ areaPathD: string;
12
+ }
13
+ export interface LineChartProps {
14
+ data: DataPoint[];
15
+ width?: string | number;
16
+ height?: string | number;
17
+ lineColor?: string;
18
+ strokeWidth?: number;
19
+ gradientStartColor?: string;
20
+ gradientEndColor?: string;
21
+ padding?: number;
22
+ animated?: boolean;
23
+ }
24
+ declare const useLineChart: (data: DataPoint[], dimensions?: UseLineChartDimensions, padding?: number) => UseLineChartReturn;
25
+ export default useLineChart;
@@ -0,0 +1,86 @@
1
+ import { useEffect, useState } from 'react';
2
+ // Helper function to generate a smooth SVG path from data points
3
+ // This uses cubic Bezier curves for smoothing.
4
+ const getSmoothLinePath = (data, width, height, padding = 20) => {
5
+ if (!data || data.length === 0 || width <= 0 || height <= 0)
6
+ return "";
7
+ // Calculate min/max for x and y values to scale the data
8
+ const minX = Math.min(...data.map(d => d.x));
9
+ const maxX = Math.max(...data.map(d => d.x));
10
+ const minY = Math.min(...data.map(d => d.y));
11
+ const maxY = Math.max(...data.map(d => d.y));
12
+ // Determine scaling factors, ensuring padding
13
+ const scaleX = (width - 2 * padding) / (maxX - minX);
14
+ const scaleY = (height - 2 * padding) / (maxY - minY);
15
+ // Function to convert data point to SVG coordinate
16
+ const toSvgCoords = (point) => {
17
+ const svgX = (point.x - minX) * scaleX + padding;
18
+ // SVG Y-axis is inverted, so we subtract from height
19
+ const svgY = height - ((point.y - minY) * scaleY + padding);
20
+ return { x: svgX, y: svgY };
21
+ };
22
+ // Convert all data points to SVG coordinates
23
+ const svgPoints = data.map(toSvgCoords);
24
+ let path = "";
25
+ if (svgPoints.length > 0) {
26
+ // Start the path at the first point
27
+ path = `M ${svgPoints[0].x},${svgPoints[0].y}`;
28
+ for (let i = 0; i < svgPoints.length - 1; i++) {
29
+ const p0 = svgPoints[i];
30
+ const p1 = svgPoints[i + 1];
31
+ // Calculate control points for cubic Bezier curve
32
+ // This is a simplified approach for smooth curves.
33
+ // For more advanced smoothing, consider D3's curve generators.
34
+ const cp1 = {
35
+ x: (p0.x + p1.x) / 2,
36
+ y: p0.y
37
+ };
38
+ const cp2 = {
39
+ x: (p0.x + p1.x) / 2,
40
+ y: p1.y
41
+ };
42
+ // Add cubic Bezier curve segment
43
+ path += ` C ${cp1.x},${cp1.y} ${cp2.x},${cp2.y} ${p1.x},${p1.y}`;
44
+ }
45
+ }
46
+ return path;
47
+ };
48
+ // Helper function to generate the SVG path for the filled area below the line
49
+ const getAreaPath = (data, width, height, padding = 20) => {
50
+ if (!data || data.length === 0)
51
+ return "";
52
+ // Reuse the line path generation logic for the top boundary of the area
53
+ const linePath = getSmoothLinePath(data, width, height, padding);
54
+ // Calculate min/max for x values to scale the data
55
+ const minX = Math.min(...data.map(d => d.x));
56
+ const maxX = Math.max(...data.map(d => d.x));
57
+ // Determine scaling factor for x
58
+ const scaleX = (width - 2 * padding) / (maxX - minX);
59
+ // Get SVG coordinates for the first and last points of the line
60
+ const firstPointSvgX = (data[0].x - minX) * scaleX + padding;
61
+ const lastPointSvgX = (data[data.length - 1].x - minX) * scaleX + padding;
62
+ // The bottom Y-coordinate of the chart, accounting for padding
63
+ const chartBottomY = height - padding;
64
+ // Construct the area path:
65
+ // 1. Start at the first point of the line path
66
+ // 2. Follow the smooth line path
67
+ // 3. Draw a vertical line down from the last point to the bottom of the chart
68
+ // 4. Draw a horizontal line across the bottom to the x-coordinate of the first point
69
+ // 5. Draw a vertical line up from the bottom to the first point's y-coordinate (closing the path)
70
+ return `${linePath} L ${lastPointSvgX},${chartBottomY} L ${firstPointSvgX},${chartBottomY} Z`;
71
+ };
72
+ // Custom React hook for the line chart logic
73
+ const useLineChart = (data, dimensions = { width: 600, height: 300 }, padding = 20) => {
74
+ const [pathD, setPathD] = useState("");
75
+ const [areaPathD, setAreaPathD] = useState("");
76
+ useEffect(() => {
77
+ // Generate the SVG path whenever data or dimensions change
78
+ const newPathD = getSmoothLinePath(data, dimensions.width, dimensions.height, padding);
79
+ setPathD(newPathD);
80
+ // Generate the area path for the gradient fill
81
+ const newAreaPathD = getAreaPath(data, dimensions.width, dimensions.height, padding);
82
+ setAreaPathD(newAreaPathD);
83
+ }, [data, dimensions.width, dimensions.height, padding]);
84
+ return { pathD, areaPathD };
85
+ };
86
+ export default useLineChart;
@@ -0,0 +1,12 @@
1
+ import { Direction } from "../types";
2
+ interface PositionOptions {
3
+ offset?: number;
4
+ direction?: Direction;
5
+ container?: HTMLElement | null;
6
+ triggerRef?: React.RefObject<HTMLElement>;
7
+ }
8
+ export declare const usePosition: (ref: React.RefObject<HTMLElement>, options?: PositionOptions) => {
9
+ postion: "fixed" | "absolute" | null;
10
+ reposition: () => void;
11
+ };
12
+ export default usePosition;
@@ -0,0 +1,191 @@
1
+ import { useCallback, useEffect, useRef } from "react"; // Added useRef for internal state if needed
2
+ export const usePosition = (ref, // The element to be positioned
3
+ options = {}) => {
4
+ const { offset = 8, direction = 'bottom', container = null, // Can be a specific parent container
5
+ triggerRef = null, // If the trigger is not the immediate parent
6
+ } = options;
7
+ // Internal state to store the applied position type
8
+ const appliedPositionRef = useRef(null);
9
+ const calculate = useCallback(() => {
10
+ const el = ref.current;
11
+ if (!el)
12
+ return;
13
+ // Determine the trigger element
14
+ const trigger = triggerRef?.current || el.parentElement;
15
+ if (!trigger) {
16
+ console.warn("usePosition: No trigger element found. Positioning may not work.");
17
+ return;
18
+ }
19
+ const rect = el.getBoundingClientRect(); // Bounding rect of the element to position
20
+ const triggerRect = trigger.getBoundingClientRect(); // Bounding rect of the trigger
21
+ // --- Determine the effective positioning context ---
22
+ let parentRect;
23
+ let targetPosition;
24
+ if (container) {
25
+ // If a specific container is provided, we assume 'absolute' positioning relative to it
26
+ // The container MUST be positioned (relative, absolute, fixed, sticky) for this to work correctly.
27
+ parentRect = container.getBoundingClientRect();
28
+ targetPosition = 'absolute';
29
+ }
30
+ else {
31
+ // No specific container provided. We need to decide if fixed or absolute to the viewport/document.
32
+ // Heuristic: If the trigger itself (or its ancestors) has 'fixed' position,
33
+ // or if the trigger is very close to the viewport edge,
34
+ // and/or if we want the positioned element to stay visible during scroll,
35
+ // we might prefer 'fixed'. Otherwise, 'absolute' is the default.
36
+ // A simple check: if the element is meant to stay in view regardless of scroll, 'fixed' is better.
37
+ // Otherwise, if it should scroll with the document, 'absolute' relative to the document.
38
+ // Let's default to 'fixed' for viewport-relative behavior if no container is provided,
39
+ // as this is often desired for elements like tooltips/dropdowns that follow the viewport.
40
+ // If the user *wants* it to scroll with content when no explicit container, they should set
41
+ // `position: absolute` and its parent *will* determine its containing block.
42
+ // For automatic calculation *without* explicitly setting parent position:
43
+ // If the `el`'s *computed* position is already `fixed`, keep it `fixed`.
44
+ // Otherwise, we will explicitly set it to `absolute` and position relative to the document.
45
+ const computedElStyle = window.getComputedStyle(el);
46
+ if (computedElStyle.position === 'fixed') {
47
+ targetPosition = 'fixed';
48
+ }
49
+ else {
50
+ // If not fixed, we will try to make it absolute relative to the document body
51
+ // (which effectively means relative to the viewport if no parent is positioned).
52
+ // We'll set el.style.position = 'absolute'
53
+ targetPosition = 'absolute';
54
+ }
55
+ // For 'fixed' positioning, the parentRect is the viewport
56
+ if (targetPosition === 'fixed') {
57
+ parentRect = {
58
+ top: 0,
59
+ left: 0,
60
+ right: window.innerWidth,
61
+ bottom: window.innerHeight,
62
+ width: window.innerWidth,
63
+ height: window.innerHeight,
64
+ };
65
+ }
66
+ else { // targetPosition === 'absolute'
67
+ // For 'absolute' positioning without a specified container,
68
+ // it implicitly positions relative to the initial containing block (viewport/document).
69
+ // So, the coordinates will still be viewport-relative.
70
+ // We need to ensure 'el' is actually positioned absolute.
71
+ parentRect = {
72
+ top: 0,
73
+ left: 0,
74
+ right: window.innerWidth,
75
+ bottom: window.innerHeight,
76
+ width: window.innerWidth,
77
+ height: window.innerHeight,
78
+ };
79
+ }
80
+ }
81
+ // Apply the determined position style if it's different from current
82
+ if (el.style.position !== targetPosition) {
83
+ el.style.position = targetPosition;
84
+ appliedPositionRef.current = targetPosition; // Store what we applied
85
+ }
86
+ else {
87
+ appliedPositionRef.current = targetPosition; // Confirm current state
88
+ }
89
+ let top = 0;
90
+ let left = 0;
91
+ // Calculate position relative to the viewport for initial placement
92
+ // These are always viewport coordinates, even if `targetPosition` becomes `absolute`
93
+ // We'll adjust based on `targetPosition` when applying to `el.style`
94
+ let viewportTop = 0;
95
+ let viewportLeft = 0;
96
+ // Determine base top/left by direction
97
+ switch (direction) {
98
+ case 'top':
99
+ viewportTop = triggerRect.top - rect.height - offset;
100
+ viewportLeft = triggerRect.left;
101
+ // Flipping logic (if it goes off-screen in the current direction)
102
+ if (viewportTop < parentRect.top && triggerRect.bottom + rect.height + offset <= parentRect.bottom) {
103
+ viewportTop = triggerRect.bottom + offset;
104
+ }
105
+ break;
106
+ case 'bottom':
107
+ viewportTop = triggerRect.bottom + offset;
108
+ viewportLeft = triggerRect.left;
109
+ // Flipping logic
110
+ if (viewportTop + rect.height > parentRect.bottom && triggerRect.top - rect.height - offset >= parentRect.top) {
111
+ viewportTop = triggerRect.top - rect.height - offset;
112
+ }
113
+ break;
114
+ case 'left':
115
+ viewportTop = triggerRect.top;
116
+ viewportLeft = triggerRect.left - rect.width - offset;
117
+ // Flipping logic
118
+ if (viewportLeft < parentRect.left && triggerRect.right + rect.width + offset <= parentRect.right) {
119
+ viewportLeft = triggerRect.right + offset;
120
+ }
121
+ break;
122
+ case 'right':
123
+ viewportTop = triggerRect.top;
124
+ viewportLeft = triggerRect.right + offset;
125
+ // Flipping logic
126
+ if (viewportLeft + rect.width > parentRect.right && triggerRect.left - rect.width - offset >= parentRect.left) {
127
+ viewportLeft = triggerRect.left - rect.width - offset;
128
+ }
129
+ break;
130
+ }
131
+ // Clamp to parent/viewport boundaries *after* potential flipping
132
+ viewportTop = Math.max(parentRect.top, Math.min(viewportTop, parentRect.bottom - rect.height));
133
+ viewportLeft = Math.max(parentRect.left, Math.min(viewportLeft, parentRect.right - rect.width));
134
+ // Apply coordinates based on the determined `targetPosition`
135
+ if (appliedPositionRef.current === 'fixed') {
136
+ // If fixed, apply viewport coordinates directly
137
+ el.style.top = `${viewportTop}px`;
138
+ el.style.left = `${viewportLeft}px`;
139
+ }
140
+ else { // 'absolute'
141
+ // If absolute, coordinates need to be relative to the closest positioned ancestor (offsetParent)
142
+ // If there's no positioned ancestor, offsetParent is body/html, so it's effectively viewport relative
143
+ // If there *is* a positioned ancestor, we need to subtract its position.
144
+ // Get the actual offset parent's bounding rect
145
+ let offsetParentRect;
146
+ if (el.offsetParent) {
147
+ offsetParentRect = el.offsetParent.getBoundingClientRect();
148
+ }
149
+ else {
150
+ // If no offsetParent, it implies relative to initial containing block (viewport)
151
+ offsetParentRect = { top: 0, left: 0 };
152
+ }
153
+ // Calculate relative coordinates
154
+ const finalTop = viewportTop - offsetParentRect.top + window.scrollY; // Add scrollY for document-relative `top`
155
+ const finalLeft = viewportLeft - offsetParentRect.left + window.scrollX; // Add scrollX for document-relative `left`
156
+ el.style.top = `${finalTop}px`;
157
+ el.style.left = `${finalLeft}px`;
158
+ }
159
+ }, [ref, triggerRef, offset, direction, container]);
160
+ useEffect(() => {
161
+ const el = ref.current;
162
+ if (!el)
163
+ return;
164
+ // Determine the trigger element
165
+ const trigger = triggerRef?.current || el.parentElement;
166
+ if (!trigger) {
167
+ console.warn("usePosition: No trigger element found. Positioning may not work.");
168
+ return;
169
+ }
170
+ // Observers and event listeners
171
+ const observer = new ResizeObserver(calculate);
172
+ observer.observe(el);
173
+ if (trigger)
174
+ observer.observe(trigger); // Observe trigger for size/position changes
175
+ calculate(); // Initial calculation
176
+ window.addEventListener('resize', calculate);
177
+ window.addEventListener('scroll', calculate, true); // Use capture phase for reliability
178
+ return () => {
179
+ observer.disconnect();
180
+ if (trigger)
181
+ observer.disconnect(); // Disconnect trigger observer as well
182
+ window.removeEventListener('resize', calculate);
183
+ window.removeEventListener('scroll', calculate, true);
184
+ };
185
+ }, [ref, direction, offset, container, triggerRef]); // Add triggerRef to dependency array
186
+ return {
187
+ postion: appliedPositionRef.current,
188
+ reposition: calculate
189
+ };
190
+ };
191
+ export default usePosition;
@@ -5,5 +5,5 @@ interface Size {
5
5
  top: number;
6
6
  left: number;
7
7
  }
8
- declare const useResizeObserver: (ref: RefObject<HTMLElement | null>) => Size;
8
+ declare const useResizeObserver: (ref: RefObject<HTMLElement | null> | HTMLElement) => Size;
9
9
  export default useResizeObserver;