@infonomic/uikit 5.2.1 → 5.3.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.
@@ -1,10 +1,15 @@
1
- import type React from 'react';
2
- type ScrollToTopIntrinsicProps = React.JSX.IntrinsicElements['button'];
3
- interface ScrollToTopProps extends ScrollToTopIntrinsicProps {
1
+ import { type ComponentProps } from 'react';
2
+ export interface ScrollToTopProps extends ComponentProps<'button'> {
3
+ /**
4
+ * The scroll position (Y-axis) in pixels that triggers the button to appear.
5
+ * @default 200
6
+ */
7
+ showAt?: number;
8
+ /**
9
+ * The target scroll position (Y-axis) to scroll to when clicked.
10
+ * @default -65
11
+ */
4
12
  offset?: number;
5
13
  }
6
- export type { ScrollToTopProps };
7
- export declare const ScrollToTop: ({ ref, offset, ...rest }: ScrollToTopProps & {
8
- ref?: React.RefObject<HTMLButtonElement>;
9
- }) => React.JSX.Element;
14
+ export declare function ScrollToTop({ className, showAt, offset, type, 'aria-label': ariaLabel, onClick, ...props }: ScrollToTopProps): import("react").JSX.Element;
10
15
  //# sourceMappingURL=scroll-to-top.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scroll-to-top.d.ts","sourceRoot":"","sources":["../../../src/components/scroll-to-top/scroll-to-top.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,KAAK,yBAAyB,GAAG,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAA;AACtE,UAAU,gBAAiB,SAAQ,yBAAyB;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,YAAY,EAAE,gBAAgB,EAAE,CAAA;AAEhC,eAAO,MAAM,WAAW,GAAwB,0BAI7C,gBAAgB,GAAG;IACpB,GAAG,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAA;CACzC,sBA2CA,CAAA"}
1
+ {"version":3,"file":"scroll-to-top.d.ts","sourceRoot":"","sources":["../../../src/components/scroll-to-top/scroll-to-top.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,cAAc,EAAuB,MAAM,OAAO,CAAA;AAIhE,MAAM,WAAW,gBAAiB,SAAQ,cAAc,CAAC,QAAQ,CAAC;IAChE;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,MAAY,EACZ,MAAY,EACZ,IAAe,EACf,YAAY,EAAE,SAA2B,EACzC,OAAO,EACP,GAAG,KAAK,EACT,EAAE,gBAAgB,+BAuDlB"}
@@ -3,43 +3,53 @@ import { jsx } from "react/jsx-runtime";
3
3
  import classnames from "classnames";
4
4
  import { useEffect, useState } from "react";
5
5
  import scroll_to_top_module from "./scroll-to-top.module.js";
6
- const scroll_to_top_ScrollToTop = function({ ref, offset = -65, ...rest }) {
6
+ function ScrollToTop({ className, showAt = 200, offset = -65, type = 'button', 'aria-label': ariaLabel = 'Scroll to top', onClick, ...props }) {
7
7
  const [show, setShow] = useState(false);
8
- const handleOnClick = ()=>{
8
+ const handleOnClick = (e)=>{
9
9
  window.scrollTo({
10
10
  top: offset,
11
11
  left: 0,
12
12
  behavior: 'smooth'
13
13
  });
14
+ onClick?.(e);
14
15
  };
15
16
  useEffect(()=>{
17
+ let ticking = false;
16
18
  const handleOnScroll = ()=>{
17
- const scrollTop = window.scrollY;
18
- scrollTop > 200 ? setShow(true) : setShow(false);
19
+ if (!ticking) {
20
+ window.requestAnimationFrame(()=>{
21
+ setShow(window.scrollY > showAt);
22
+ ticking = false;
23
+ });
24
+ ticking = true;
25
+ }
19
26
  };
20
- if ('undefined' != typeof window) window.addEventListener('scroll', handleOnScroll);
27
+ window.addEventListener('scroll', handleOnScroll, {
28
+ passive: true
29
+ });
21
30
  return ()=>{
22
31
  window.removeEventListener('scroll', handleOnScroll);
23
32
  };
24
- }, []);
33
+ }, [
34
+ showAt
35
+ ]);
25
36
  return /*#__PURE__*/ jsx("button", {
26
- ref: ref,
27
- ...rest,
37
+ type: type,
38
+ "aria-label": ariaLabel,
28
39
  onClick: handleOnClick,
29
- type: "button",
30
- id: "scroll-to-top",
31
40
  className: classnames('scroll-to-top', scroll_to_top_module["scroll-to-top"], {
32
41
  'scroll-to-top-shown': show,
33
42
  [scroll_to_top_module["scroll-to-top-shown"]]: show
34
- }),
43
+ }, className),
44
+ ...props,
35
45
  children: /*#__PURE__*/ jsx("span", {
46
+ "aria-hidden": "true",
36
47
  children: /*#__PURE__*/ jsx("svg", {
37
48
  className: "icon",
38
49
  style: {
39
50
  fill: 'currentColor'
40
51
  },
41
52
  focusable: "false",
42
- "aria-hidden": "true",
43
53
  viewBox: "0 0 51 32",
44
54
  children: /*#__PURE__*/ jsx("path", {
45
55
  d: "M25.4,9.8L45.6,30l4.5-4.5L25.4,0.8L0.8,25.4L5.3,30L25.4,9.8z"
@@ -47,5 +57,5 @@ const scroll_to_top_ScrollToTop = function({ ref, offset = -65, ...rest }) {
47
57
  })
48
58
  })
49
59
  });
50
- };
51
- export { scroll_to_top_ScrollToTop as ScrollToTop };
60
+ }
61
+ export { ScrollToTop };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@infonomic/uikit",
3
3
  "private": false,
4
4
  "license": "MIT",
5
- "version": "5.2.1",
5
+ "version": "5.3.0",
6
6
  "type": "module",
7
7
  "description": "Infonomic UI kit is a collection of reusable UI components and utilities for React and Astro.",
8
8
  "keywords": [
package/src/astro.d.ts CHANGED
@@ -14,6 +14,7 @@ import type InputComponent from './components/forms/input.astro'
14
14
  import type InputAdornmentComponent from './components/forms/input-adornment.astro'
15
15
  import type LabelComponent from './components/forms/label.astro'
16
16
  import type SectionComponent from './components/section/section.astro'
17
+ import type ScrollToTopComponent from './components/scroll-to-top/scroll-to-top.astro'
17
18
  import type CloseIconComponent from './icons/close-icon.astro'
18
19
  import type IconElementComponent from './icons/icon-element.astro'
19
20
  import type LightIconComponent from './icons/light-icon.astro'
@@ -41,3 +42,4 @@ export declare const LightIcon: typeof LightIconComponent
41
42
  export declare const MoonIcon: typeof MoonIconComponent
42
43
  export declare const SearchIcon: typeof SearchIconComponent
43
44
  export declare const Section: typeof SectionComponent
45
+ export declare const ScrollToTop: typeof ScrollToTopComponent
package/src/astro.js CHANGED
@@ -14,12 +14,14 @@ import InputComponent from './components/forms/input.astro'
14
14
  import InputAdornmentComponent from './components/forms/input-adornment.astro'
15
15
  import LabelComponent from './components/forms/label.astro'
16
16
  import SectionComponent from './components/section/section.astro'
17
+ import ScrollToTopComponent from './components/scroll-to-top/scroll-to-top.astro'
17
18
  import CloseIconComponent from './icons/close-icon.astro'
18
19
  import IconElementComponent from './icons/icon-element.astro'
19
20
  import LightIconComponent from './icons/light-icon.astro'
20
21
  import MoonIconComponent from './icons/moon-icon.astro'
21
22
  import SearchIconComponent from './icons/search-icon.astro'
22
23
 
24
+
23
25
  export const Button = ButtonComponent
24
26
  export const Hamburger = HamburgerComponent
25
27
  export const IconButton = IconButtonComponent
@@ -36,6 +38,7 @@ export const InputAdornment = InputAdornmentComponent
36
38
  export const Input = InputComponent
37
39
  export const Label = LabelComponent
38
40
  export const Section = SectionComponent
41
+ export const ScrollToTop = ScrollToTopComponent
39
42
  export const IconElement = IconElementComponent
40
43
  export const LightIcon = LightIconComponent
41
44
  export const MoonIcon = MoonIconComponent
@@ -0,0 +1,75 @@
1
+ ---
2
+ import styles from './scroll-to-top.module.css'
3
+
4
+ interface Props {
5
+ class?: string
6
+ showAt?: number
7
+ offset?: number
8
+ 'aria-label'?: string
9
+ [key: string]: any
10
+ }
11
+
12
+ const {
13
+ class: className,
14
+ showAt = 200,
15
+ offset = -65,
16
+ 'aria-label': ariaLabel = 'Scroll to top',
17
+ ...rest
18
+ } = Astro.props
19
+ ---
20
+
21
+ <button
22
+ type="button"
23
+ aria-label={ariaLabel}
24
+ class:list={['scroll-to-top', styles['scroll-to-top'], className]}
25
+ data-show-at={showAt}
26
+ data-offset={offset}
27
+ data-class-shown={styles['scroll-to-top-shown']}
28
+ {...rest}
29
+ >
30
+ <span aria-hidden="true">
31
+ <svg class="icon" style="fill: currentColor" focusable="false" viewBox="0 0 51 32">
32
+ <path d="M25.4,9.8L45.6,30l4.5-4.5L25.4,0.8L0.8,25.4L5.3,30L25.4,9.8z"></path>
33
+ </svg>
34
+ </span>
35
+ </button>
36
+
37
+ <script>
38
+ const buttons = document.querySelectorAll('button.scroll-to-top')
39
+
40
+ buttons.forEach((button) => {
41
+ if (!(button instanceof HTMLElement)) return
42
+
43
+ const showAt = Number(button.dataset.showAt) || 200
44
+ const offset = Number(button.dataset.offset) || -65
45
+ const classShown = button.dataset.classShown
46
+
47
+ if (classShown == null) return
48
+
49
+ // Click Handler
50
+ button.addEventListener('click', () => {
51
+ window.scrollTo({ top: offset, left: 0, behavior: 'smooth' })
52
+ })
53
+
54
+ // Scroll Handler
55
+ let ticking = false
56
+ const handleOnScroll = () => {
57
+ if (!ticking) {
58
+ window.requestAnimationFrame(() => {
59
+ const shouldShow = window.scrollY > showAt
60
+ if (shouldShow) {
61
+ button.classList.add(classShown)
62
+ button.classList.add('scroll-to-top-shown')
63
+ } else {
64
+ button.classList.remove(classShown)
65
+ button.classList.remove('scroll-to-top-shown')
66
+ }
67
+ ticking = false
68
+ })
69
+ ticking = true
70
+ }
71
+ }
72
+
73
+ window.addEventListener('scroll', handleOnScroll, { passive: true })
74
+ })
75
+ </script>
@@ -1,62 +1,81 @@
1
1
  'use client'
2
2
 
3
- import type React from 'react'
4
3
  import cx from 'classnames'
5
- import { useEffect, useState } from 'react'
4
+ import { type ComponentProps, useEffect, useState } from 'react'
6
5
 
7
- import style from './scroll-to-top.module.css'
6
+ import styles from './scroll-to-top.module.css'
8
7
 
9
- type ScrollToTopIntrinsicProps = React.JSX.IntrinsicElements['button']
10
- interface ScrollToTopProps extends ScrollToTopIntrinsicProps {
8
+ export interface ScrollToTopProps extends ComponentProps<'button'> {
9
+ /**
10
+ * The scroll position (Y-axis) in pixels that triggers the button to appear.
11
+ * @default 200
12
+ */
13
+ showAt?: number
14
+ /**
15
+ * The target scroll position (Y-axis) to scroll to when clicked.
16
+ * @default -65
17
+ */
11
18
  offset?: number
12
19
  }
13
20
 
14
- export type { ScrollToTopProps }
15
-
16
- export const ScrollToTop = function ScrollToTop({
17
- ref,
21
+ export function ScrollToTop({
22
+ className,
23
+ showAt = 200,
18
24
  offset = -65,
19
- ...rest
20
- }: ScrollToTopProps & {
21
- ref?: React.RefObject<HTMLButtonElement>
22
- }) {
25
+ type = 'button',
26
+ 'aria-label': ariaLabel = 'Scroll to top',
27
+ onClick,
28
+ ...props
29
+ }: ScrollToTopProps) {
23
30
  const [show, setShow] = useState(false)
24
31
 
25
- const handleOnClick = (): void => {
32
+ const handleOnClick = (e: React.MouseEvent<HTMLButtonElement>): void => {
26
33
  window.scrollTo({ top: offset, left: 0, behavior: 'smooth' })
34
+ onClick?.(e)
27
35
  }
28
36
 
29
37
  useEffect(() => {
38
+ let ticking = false
39
+
30
40
  const handleOnScroll = (): void => {
31
- const scrollTop = window.scrollY
32
- if (scrollTop > 200) {
33
- setShow(true)
34
- } else {
35
- setShow(false)
41
+ if (!ticking) {
42
+ window.requestAnimationFrame(() => {
43
+ setShow(window.scrollY > showAt)
44
+ ticking = false
45
+ })
46
+ ticking = true
36
47
  }
37
48
  }
38
- if (typeof window !== 'undefined') {
39
- window.addEventListener('scroll', handleOnScroll)
40
- }
49
+
50
+ window.addEventListener('scroll', handleOnScroll, { passive: true })
41
51
  return () => {
42
52
  window.removeEventListener('scroll', handleOnScroll)
43
53
  }
44
- }, [])
54
+ }, [showAt])
45
55
 
46
56
  return (
47
57
  <button
48
- ref={ref}
49
- {...rest}
58
+ type={type}
59
+ aria-label={ariaLabel}
50
60
  onClick={handleOnClick}
51
- type="button"
52
- id="scroll-to-top"
53
- className={cx('scroll-to-top', style['scroll-to-top'], {
54
- 'scroll-to-top-shown': show,
55
- [style['scroll-to-top-shown']]: show,
56
- })}
61
+ className={cx(
62
+ 'scroll-to-top',
63
+ styles['scroll-to-top'],
64
+ {
65
+ 'scroll-to-top-shown': show,
66
+ [styles['scroll-to-top-shown']]: show,
67
+ },
68
+ className
69
+ )}
70
+ {...props}
57
71
  >
58
- <span>
59
- <svg className="icon" style={{fill: 'currentColor'}} focusable="false" aria-hidden="true" viewBox="0 0 51 32">
72
+ <span aria-hidden="true">
73
+ <svg
74
+ className="icon"
75
+ style={{ fill: 'currentColor' }}
76
+ focusable="false"
77
+ viewBox="0 0 51 32"
78
+ >
60
79
  <path d="M25.4,9.8L45.6,30l4.5-4.5L25.4,0.8L0.8,25.4L5.3,30L25.4,9.8z" />
61
80
  </svg>
62
81
  </span>