@heliosgraphics/ui 2.0.0-alpha.89 → 2.0.0-alpha.90

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 (90) hide show
  1. package/components/Alert/Alert.tsx +1 -1
  2. package/components/Breadcrumb/Breadcrumb.meta.ts +1 -1
  3. package/components/Breadcrumb/Breadcrumb.tsx +4 -4
  4. package/components/Browser/Browser.tsx +6 -2
  5. package/components/Button/Button.tsx +175 -173
  6. package/components/ButtonGroup/ButtonGroup.tsx +2 -4
  7. package/components/Checkbox/Checkbox.tsx +61 -55
  8. package/components/Clock/Clock.module.css +1 -1
  9. package/components/Clock/Clock.tsx +1 -1
  10. package/components/Column/Column.tsx +2 -2
  11. package/components/Confirm/Confirm.tsx +1 -1
  12. package/components/DatePicker/DatePicker.tsx +6 -2
  13. package/components/Debug/Debug.tsx +2 -2
  14. package/components/Details/Details.tsx +1 -1
  15. package/components/Dialog/Dialog.tsx +32 -5
  16. package/components/Donut/Donut.module.css +1 -1
  17. package/components/Donut/Donut.tsx +8 -1
  18. package/components/Dot/Dot.tsx +1 -1
  19. package/components/Dropdown/Dropdown.tsx +46 -19
  20. package/components/Fieldset/Fieldset.tsx +1 -1
  21. package/components/Flex/Flex.tsx +12 -10
  22. package/components/Grid/Grid.tsx +7 -3
  23. package/components/Heading/Heading.tsx +15 -10
  24. package/components/Heading/components/H0/H0.tsx +1 -1
  25. package/components/Icon/Icon.tsx +1 -1
  26. package/components/Input/Input.tsx +13 -3
  27. package/components/Layout/components/LayoutAside/LayoutAside.tsx +1 -1
  28. package/components/Layout/components/LayoutAside/components/LayoutAsideContent/LayoutAsideContent.tsx +1 -1
  29. package/components/Layout/components/LayoutAside/components/LayoutAsideFooter/LayoutAsideFooter.tsx +1 -1
  30. package/components/Layout/components/LayoutAside/components/LayoutAsideToggle/LayoutAsideToggle.tsx +1 -1
  31. package/components/Layout/components/LayoutMain/LayoutMain.tsx +1 -1
  32. package/components/Layout/components/LayoutMain/components/LayoutMainContent/LayoutMainContent.tsx +1 -1
  33. package/components/Layout/components/LayoutNavigation/LayoutNavigation.tsx +1 -1
  34. package/components/Loading/Loading.tsx +10 -2
  35. package/components/Markdown/Markdown.meta.ts +7 -0
  36. package/components/Markdown/Markdown.tsx +15 -6
  37. package/components/Markdown/Markdown.types.ts +4 -1
  38. package/components/Markdown/Markdown.utils.spec.ts +3 -3
  39. package/components/Markdown/Markdown.utils.ts +9 -7
  40. package/components/Masonry/Masonry.meta.ts +1 -1
  41. package/components/Masonry/Masonry.tsx +2 -2
  42. package/components/Masonry/Masonry.types.ts +1 -1
  43. package/components/Menu/Menu.tsx +1 -3
  44. package/components/Menu/components/MenuCategory/MenuCategory.tsx +13 -2
  45. package/components/Menu/components/MenuFilter/MenuFilter.tsx +1 -1
  46. package/components/Menu/components/MenuItem/MenuItem.tsx +14 -1
  47. package/components/Overlay/Overlay.tsx +1 -1
  48. package/components/Pie/Pie.tsx +7 -4
  49. package/components/Pill/Pill.tsx +1 -1
  50. package/components/Placeholder/Placeholder.tsx +1 -0
  51. package/components/Progress/Progress.tsx +9 -1
  52. package/components/Radio/Radio.tsx +64 -58
  53. package/components/Segments/Segments.tsx +6 -6
  54. package/components/Select/Select.tsx +5 -4
  55. package/components/Separator/components/HorizontalSeparator/HorizontalSeparator.tsx +1 -1
  56. package/components/Separator/components/VerticalSeparator/VerticalSeparator.tsx +1 -1
  57. package/{components.css → components/Setup/Setup.components.css} +2 -2
  58. package/components/Setup/Setup.meta.ts +2 -2
  59. package/components/Setup/Setup.tsx +10 -7
  60. package/components/Setup/Setup.types.ts +1 -0
  61. package/components/Shimmer/Shimmer.tsx +3 -2
  62. package/components/Slider/Slider.tsx +4 -4
  63. package/components/Spacer/Spacer.tsx +1 -1
  64. package/components/Table/Table.tsx +1 -1
  65. package/components/Tabs/Tabs.tsx +36 -12
  66. package/components/Text/Text.tsx +5 -2
  67. package/components/Textarea/Textarea.tsx +10 -5
  68. package/components/Tile/Tile.tsx +2 -9
  69. package/components/Tile/Tile.types.ts +1 -1
  70. package/components/Timestamp/Timestamp.meta.ts +5 -1
  71. package/components/Timestamp/Timestamp.tsx +3 -3
  72. package/components/Timestamp/Timestamp.types.ts +1 -1
  73. package/components/Toggle/Toggle.tsx +2 -2
  74. package/components/Tooltip/Tooltip.module.css +32 -0
  75. package/components/Tooltip/Tooltip.tsx +36 -72
  76. package/components/Tooltip/Tooltip.types.ts +1 -0
  77. package/components/Tooltip/components/TooltipContent/TooltipContent.tsx +5 -3
  78. package/components/Tooltip/components/TooltipTrigger/TooltipTrigger.tsx +10 -4
  79. package/components/shared/InputLabel/InputLabel.tsx +1 -1
  80. package/components/shared/ResultList/ResultList.tsx +6 -7
  81. package/constants/hooks.ts +1 -1
  82. package/contexts/LayoutContext/LayoutContext.tsx +30 -20
  83. package/contexts/LayoutContext/LayoutContext.types.ts +1 -0
  84. package/hooks/useDebounce.tsx +2 -2
  85. package/hooks/useLayoutContext.tsx +1 -0
  86. package/hooks/usePrint.tsx +31 -0
  87. package/index.ts +4 -0
  88. package/package.json +20 -12
  89. package/utils/date.ts +54 -0
  90. package/utils/dayjs.ts +0 -21
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import { getClasses } from "@heliosgraphics/utils/classnames"
3
+ import { getClasses } from "@heliosgraphics/utils"
4
4
  import { Text } from "../Text"
5
5
  import { useId, useRef, useEffect } from "react"
6
6
  import { InputLabel } from "../shared/InputLabel"
@@ -13,6 +13,7 @@ export const Textarea: FC<TextareaProps> = (props) => {
13
13
  const { autoComplete: _autoComplete, helperText: _helperText, isDisabled, isLabelHidden, ...goodProps } = props
14
14
 
15
15
  const htmlFor: string = useId()
16
+ const helperId: string = `${htmlFor}-helper`
16
17
  const textareaClasses: string = getClasses(styles.textarea, "flex flex-column", {
17
18
  [styles.textareaDisabled]: props.isDisabled,
18
19
  })
@@ -24,17 +25,20 @@ export const Textarea: FC<TextareaProps> = (props) => {
24
25
  }
25
26
  }
26
27
 
27
- // TODO no longer necessary <3
28
28
  useEffect(() => {
29
29
  if (textareaRef?.current) {
30
30
  onInput()
31
31
  }
32
32
 
33
- setTimeout(() => onInput(), 100)
33
+ const timer: ReturnType<typeof setTimeout> = setTimeout(() => onInput(), 100)
34
+
35
+ return (): void => {
36
+ globalThis.clearTimeout(timer)
37
+ }
34
38
  }, [props.value])
35
39
 
36
40
  return (
37
- <div className={textareaClasses}>
41
+ <div className={textareaClasses} data-ui-component="Textarea">
38
42
  {!props.isLabelHidden && (
39
43
  <InputLabel
40
44
  id={htmlFor}
@@ -51,9 +55,10 @@ export const Textarea: FC<TextareaProps> = (props) => {
51
55
  onInput={onInput}
52
56
  onLoad={onInput}
53
57
  disabled={isDisabled}
58
+ aria-describedby={props.helperText ? helperId : undefined}
54
59
  />
55
60
  {!!props.helperText && (
56
- <Text type="tiny" emphasis="tertiary" className={styles.input__helper}>
61
+ <Text type="tiny" emphasis="tertiary" className={styles.input__helper} id={helperId}>
57
62
  {props.helperText}
58
63
  </Text>
59
64
  )}
@@ -1,5 +1,4 @@
1
- "use client"
2
- import { getClasses } from "@heliosgraphics/utils/classnames"
1
+ import { getClasses } from "@heliosgraphics/utils"
3
2
  import { getColorClasses } from "../../utils/colors"
4
3
  import { Icon } from "../Icon"
5
4
  import { Text } from "../Text"
@@ -35,6 +34,7 @@ export const Tile: FC<TileProps> = ({
35
34
  <Flex
36
35
  {...(onClick && { onClick })}
37
36
  className={tileClasses}
37
+ data-ui-component="Tile"
38
38
  isCentered={true}
39
39
  isColumn={true}
40
40
  style={{
@@ -44,13 +44,6 @@ export const Tile: FC<TileProps> = ({
44
44
  }}
45
45
  >
46
46
  <Icon icon={icon} size={size * 0.5} emphasis="inherit" />
47
- {/*<svg width="100%" height="100%" viewBox={`0 0 ${size} ${size}`} className={styles.tile__background}>
48
- <linearGradient id={tileId}>
49
- <stop stopColor={`var(--${resolvedColor})`} offset="0%" />
50
- {colorAccent && <stop stopColor={`var(--${resolvedColorAccent})`} offset="100%" />}
51
- </linearGradient>
52
- <rect fill={`url(#${tileId})`} x={0} y={0} width={size} height={size} rx={isRound ? size : isRounded ? 8 : 0} />
53
- </svg> */}
54
47
  {iconAccent && (
55
48
  <Flex isCentered={true}>
56
49
  <Icon icon={iconAccent} size={size * 1} className={styles.tile__iconAccent} />
@@ -1,4 +1,4 @@
1
- import { MouseEventHandler } from "react"
1
+ import type { MouseEventHandler } from "react"
2
2
  import type { HeliosIconType } from "../../types/icons"
3
3
  import type { HeliosColorType, HeliosAppearanceType } from "../../types/colors"
4
4
 
@@ -12,7 +12,11 @@ export const meta: HeliosAttributeMeta<TimestampProps> = {
12
12
  _status: "nominal",
13
13
  _category: "content",
14
14
  date: { type: "string", isOptional: true },
15
- format: { type: "string", isOptional: true, default: "MMMM D, YYYY" },
15
+ format: {
16
+ type: "Intl.DateTimeFormatOptions",
17
+ isOptional: true,
18
+ default: "{ year: numeric, month: long, day: numeric }",
19
+ },
16
20
  fromNow: { type: "boolean", isOptional: true },
17
21
  text: { type: "string", isOptional: true },
18
22
  }
@@ -1,8 +1,8 @@
1
- import { getIsValid, formatDate, getFromNow } from "../../utils/dayjs"
1
+ import { getIsValid, formatDate, getFromNow } from "../../utils/date"
2
2
  import type { FC } from "react"
3
3
  import type { TimestampProps } from "./Timestamp.types"
4
4
 
5
- export const Timestamp: FC<TimestampProps> = ({ date, fromNow, text, format = "MMMM D, YYYY" }) => {
5
+ export const Timestamp: FC<TimestampProps> = ({ date, fromNow, text, format }) => {
6
6
  const fromNowDate = getFromNow(date)
7
7
  const isValid: boolean = getIsValid(date)
8
8
  const parsedIsoString: string = date && isValid ? new Date(Date.parse(date))?.toISOString() : ""
@@ -11,7 +11,7 @@ export const Timestamp: FC<TimestampProps> = ({ date, fromNow, text, format = "M
11
11
  const formattedDate: string = fromNow ? fromNowDate : formatDate(date, format)
12
12
 
13
13
  return (
14
- <time dateTime={timestampDate} suppressHydrationWarning={true}>
14
+ <time dateTime={timestampDate} suppressHydrationWarning={true} data-ui-component="Timestamp">
15
15
  {!!text && text}
16
16
  {formattedDate}
17
17
  </time>
@@ -1,6 +1,6 @@
1
1
  export interface TimestampProps {
2
2
  date?: string
3
- format?: string
3
+ format?: Intl.DateTimeFormatOptions
4
4
  fromNow?: boolean
5
5
  text?: string
6
6
  }
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import { useId } from "react"
4
- import { getClasses } from "@heliosgraphics/utils/classnames"
4
+ import { getClasses } from "@heliosgraphics/utils"
5
5
  import { getColorClasses } from "../../utils/colors"
6
6
  import { INTENTION_COLOR_MAP } from "../../constants/intentions"
7
7
  import { Text } from "../Text"
@@ -35,7 +35,7 @@ export const Toggle: FC<ToggleProps> = ({
35
35
  const toggleLabelClasses = getClasses(styles.toggle__toggleLabel, "flex flex-y-center gap-4 flex-wrap")
36
36
 
37
37
  return (
38
- <div className={toggleClasses}>
38
+ <div className={toggleClasses} data-ui-component="Toggle">
39
39
  <label className={toggleLabelClasses} htmlFor={toggleId}>
40
40
  <input
41
41
  type="checkbox"
@@ -2,6 +2,7 @@
2
2
  position: relative;
3
3
 
4
4
  display: inline-block;
5
+ anchor-scope: --tooltip;
5
6
  }
6
7
 
7
8
  .tooltip {
@@ -9,10 +10,41 @@
9
10
  z-index: 10;
10
11
 
11
12
  max-width: 320px;
13
+ margin: 8px;
12
14
  padding: 8px;
13
15
 
14
16
  background-color: var(--ui-bg-secondary);
15
17
  border: 0;
16
18
  color: var(--ui-text-primary);
17
19
  border-radius: var(--radius-sm);
20
+
21
+ position-area: bottom center;
22
+ }
23
+
24
+ .tooltipBottomLeft {
25
+ position-area: bottom left;
26
+ }
27
+
28
+ .tooltipBottomRight {
29
+ position-area: bottom right;
30
+ }
31
+
32
+ .tooltipTopCenter {
33
+ position-area: top center;
34
+ }
35
+
36
+ .tooltipTopLeft {
37
+ position-area: top left;
38
+ }
39
+
40
+ .tooltipTopRight {
41
+ position-area: top right;
42
+ }
43
+
44
+ .tooltipLeftCenter {
45
+ position-area: left center;
46
+ }
47
+
48
+ .tooltipRightCenter {
49
+ position-area: right center;
18
50
  }
@@ -1,19 +1,37 @@
1
1
  "use client"
2
2
 
3
- import { useRef, useEffect, useMemo, useState, FC } from "react"
4
- import { getClasses } from "@heliosgraphics/utils/classnames"
3
+ import { useRef, useEffect, useMemo, useState, useId, type FC } from "react"
4
+ import { getClasses } from "@heliosgraphics/utils"
5
5
  import styles from "./Tooltip.module.css"
6
6
  import type { TooltipProps, TooltipComposition, TooltipContextType } from "./Tooltip.types"
7
7
  import { TooltipTrigger } from "./components/TooltipTrigger"
8
8
  import { TooltipContent } from "./components/TooltipContent"
9
9
  import { TooltipContext } from "./Tooltip.utils"
10
10
 
11
+ const POSITION_CLASS_MAP: Record<NonNullable<TooltipProps["position"]>, string | undefined> = {
12
+ "bottom-center": undefined,
13
+ "bottom-left": styles.tooltipBottomLeft,
14
+ "bottom-right": styles.tooltipBottomRight,
15
+ "top-center": styles.tooltipTopCenter,
16
+ "top-left": styles.tooltipTopLeft,
17
+ "top-right": styles.tooltipTopRight,
18
+ "left-center": styles.tooltipLeftCenter,
19
+ "right-center": styles.tooltipRightCenter,
20
+ }
21
+
11
22
  const TooltipComponent: FC<TooltipProps> = ({ children = null, position = "bottom-center", isVisible = false }) => {
12
23
  const [isOpen, setIsOpen] = useState<boolean>(isVisible)
13
24
  const triggerRef = useRef<HTMLDivElement | null>(null)
14
25
  const popoverRef = useRef<HTMLDivElement | null>(null)
26
+ const id: string = useId()
27
+ const anchorName: string = `--tooltip-${id.replace(/:/g, "")}`
28
+
29
+ const positionClass: string | undefined = POSITION_CLASS_MAP[position]
15
30
 
16
- const tooltipClasses: string = useMemo(() => getClasses(styles.tooltip, "elevation-medium"), [])
31
+ const tooltipClasses: string = useMemo(
32
+ () => getClasses(styles.tooltip, "elevation-md", positionClass),
33
+ [positionClass],
34
+ )
17
35
 
18
36
  useEffect(() => {
19
37
  const trigger = triggerRef.current
@@ -26,69 +44,14 @@ const TooltipComponent: FC<TooltipProps> = ({ children = null, position = "botto
26
44
  return
27
45
  }
28
46
 
29
- const positionTooltip = (): void => {
30
- if (!trigger || !popover) return
31
-
32
- const triggerRect = trigger.getBoundingClientRect()
33
- const popoverRect = popover.getBoundingClientRect()
34
- const viewportWidth = globalThis.innerWidth
35
- const viewportHeight = globalThis.innerHeight
36
-
37
- let top = 0
38
- let left = 0
39
-
40
- switch (position) {
41
- case "bottom-center":
42
- top = triggerRect.bottom + 8
43
- left = triggerRect.left + triggerRect.width / 2 - popoverRect.width / 2
44
- break
45
- case "bottom-left":
46
- top = triggerRect.bottom + 8
47
- left = triggerRect.left
48
- break
49
- case "bottom-right":
50
- top = triggerRect.bottom + 8
51
- left = triggerRect.right - popoverRect.width
52
- break
53
- case "top-center":
54
- top = triggerRect.top - popoverRect.height - 8
55
- left = triggerRect.left + triggerRect.width / 2 - popoverRect.width / 2
56
- break
57
- case "top-left":
58
- top = triggerRect.top - popoverRect.height - 8
59
- left = triggerRect.left
60
- break
61
- case "top-right":
62
- top = triggerRect.top - popoverRect.height - 8
63
- left = triggerRect.right - popoverRect.width
64
- break
65
- case "left-center":
66
- top = triggerRect.top + triggerRect.height / 2 - popoverRect.height / 2
67
- left = triggerRect.left - popoverRect.width - 8
68
- break
69
- case "right-center":
70
- top = triggerRect.top + triggerRect.height / 2 - popoverRect.height / 2
71
- left = triggerRect.right + 8
72
- break
73
- }
74
-
75
- left = Math.max(10, Math.min(left, viewportWidth - popoverRect.width - 10))
76
- top = Math.max(10, Math.min(top, viewportHeight - popoverRect.height - 10))
77
-
78
- popover.style.top = `${top}px`
79
- popover.style.left = `${left}px`
80
- }
81
-
82
47
  if (isVisible) {
83
48
  popover.showPopover()
84
49
  setIsOpen(true)
85
- setTimeout(positionTooltip, 0)
86
50
  }
87
51
 
88
52
  const handleMouseEnter = (): void => {
89
53
  popover.showPopover()
90
54
  setIsOpen(true)
91
- setTimeout(positionTooltip, 0)
92
55
  }
93
56
 
94
57
  const handleMouseLeave = (): void => {
@@ -109,28 +72,29 @@ const TooltipComponent: FC<TooltipProps> = ({ children = null, position = "botto
109
72
  trigger.addEventListener("mouseleave", handleMouseLeave)
110
73
  trigger.addEventListener("keydown", handleKeyDown)
111
74
 
112
- globalThis.addEventListener("resize", positionTooltip)
113
- globalThis.addEventListener("scroll", positionTooltip, true)
114
-
115
75
  return (): void => {
116
76
  trigger.removeEventListener("mouseenter", handleMouseEnter)
117
77
  trigger.removeEventListener("mouseleave", handleMouseLeave)
118
78
  trigger.removeEventListener("keydown", handleKeyDown)
119
- globalThis.removeEventListener("resize", positionTooltip)
120
- globalThis.removeEventListener("scroll", positionTooltip, true)
121
79
  }
122
- }, [isVisible, position])
123
-
124
- const contextValue: TooltipContextType = {
125
- triggerRef,
126
- popoverRef,
127
- tooltipClasses,
128
- isOpen,
129
- }
80
+ }, [isVisible])
81
+
82
+ const contextValue: TooltipContextType = useMemo(
83
+ () => ({
84
+ triggerRef,
85
+ popoverRef,
86
+ tooltipClasses,
87
+ anchorName,
88
+ isOpen,
89
+ }),
90
+ [tooltipClasses, anchorName, isOpen],
91
+ )
130
92
 
131
93
  return (
132
94
  <TooltipContext.Provider value={contextValue}>
133
- <div className={styles.tooltipContainer}>{children}</div>
95
+ <div className={styles.tooltipContainer} data-ui-component="Tooltip">
96
+ {children}
97
+ </div>
134
98
  </TooltipContext.Provider>
135
99
  )
136
100
  }
@@ -21,6 +21,7 @@ export interface TooltipContextType {
21
21
  triggerRef: RefObject<HTMLDivElement | null>
22
22
  popoverRef: RefObject<HTMLDivElement | null>
23
23
  tooltipClasses: string
24
+ anchorName: string
24
25
  isOpen: boolean
25
26
  }
26
27
 
@@ -1,13 +1,15 @@
1
1
  import { Text } from "../../../Text"
2
2
  import { useTooltipContext } from "../../Tooltip.utils"
3
- import type { FC } from "react"
3
+ import type { CSSProperties, FC } from "react"
4
4
  import type { TooltipContentProps } from "./TooltipContent.types"
5
5
 
6
6
  export const TooltipContent: FC<TooltipContentProps> = ({ children }) => {
7
- const { popoverRef, tooltipClasses } = useTooltipContext()
7
+ const { popoverRef, tooltipClasses, anchorName } = useTooltipContext()
8
+
9
+ const contentStyle: CSSProperties = { positionAnchor: anchorName } as CSSProperties
8
10
 
9
11
  return (
10
- <div className={tooltipClasses} ref={popoverRef} popover="manual">
12
+ <div className={tooltipClasses} ref={popoverRef} popover="manual" style={contentStyle}>
11
13
  <Text type="small">{children}</Text>
12
14
  </div>
13
15
  )
@@ -1,9 +1,15 @@
1
- import { TooltipTriggerProps } from "./TooltipTrigger.types"
1
+ import type { TooltipTriggerProps } from "./TooltipTrigger.types"
2
2
  import { useTooltipContext } from "../../Tooltip.utils"
3
- import type { FC } from "react"
3
+ import type { CSSProperties, FC } from "react"
4
4
 
5
5
  export const TooltipTrigger: FC<TooltipTriggerProps> = ({ children }) => {
6
- const { triggerRef } = useTooltipContext()
6
+ const { triggerRef, anchorName } = useTooltipContext()
7
7
 
8
- return <div ref={triggerRef}>{children}</div>
8
+ const triggerStyle: CSSProperties = { anchorName } as CSSProperties
9
+
10
+ return (
11
+ <div ref={triggerRef} style={triggerStyle}>
12
+ {children}
13
+ </div>
14
+ )
9
15
  }
@@ -1,6 +1,6 @@
1
1
  import { type FC } from "react"
2
2
  import styles from "./InputLabel.module.css"
3
- import { getClasses } from "@heliosgraphics/utils/classnames"
3
+ import { getClasses } from "@heliosgraphics/utils"
4
4
  import type { InputLabelProps } from "./InputLabel.types"
5
5
 
6
6
  export const InputLabel: FC<InputLabelProps> = ({ id, label, isDisabled, isHidden }) => {
@@ -1,4 +1,4 @@
1
- import { getClasses } from "@heliosgraphics/utils/classnames"
1
+ import { getClasses } from "@heliosgraphics/utils"
2
2
  import { Checkbox } from "../../Checkbox"
3
3
  import { Flex } from "../../Flex"
4
4
  import { Icon } from "../../Icon"
@@ -15,23 +15,22 @@ export const ResultList = forwardRef<HTMLOListElement, ResultListProps>(({ items
15
15
 
16
16
  return (
17
17
  <ol className={resultListClasses} data-ui-component="ResultList" role="listbox" ref={ref}>
18
- {items?.map((item, key) => {
18
+ {items?.map((item, index) => {
19
+ const itemKey: string = item.name ? `${item.name}-${index}` : `separator-${index}`
19
20
  const itemClasses: string = getClasses("flex flex-y-center gap-5", styles.item, {
20
21
  [styles.itemActive]: item.isActive && !item.type,
21
22
  [styles.itemDisabled]: item.isDisabled,
22
23
  })
23
24
 
24
- // NOTE @03b8 future composit
25
- if (item.type === "separator") return <li key={key} className={styles.resultList__separator} />
25
+ if (item.type === "separator") return <li key={itemKey} className={styles.resultList__separator} />
26
26
 
27
- // NOTE @03b8 future composit
28
27
  if (item.type === "checkbox") {
29
28
  const onCheck = (event: ChangeEvent<HTMLInputElement>): void =>
30
29
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
30
  item.onClick?.(event as any)
32
31
 
33
32
  return (
34
- <li key={key} onClick={item.onClick} className={itemClasses}>
33
+ <li key={itemKey} onClick={item.onClick} className={itemClasses}>
35
34
  <Checkbox
36
35
  label={item.name}
37
36
  {...(item.description && { description: item.description })}
@@ -43,7 +42,7 @@ export const ResultList = forwardRef<HTMLOListElement, ResultListProps>(({ items
43
42
  }
44
43
 
45
44
  return (
46
- <li key={key} onClick={item.onClick} className={itemClasses}>
45
+ <li key={itemKey} onClick={item.onClick} className={itemClasses}>
47
46
  {item.icon && (
48
47
  <Flex isCentered={true}>
49
48
  <Icon icon={item.icon} size={18} />
@@ -1 +1 @@
1
- export const HOOKS = ["useIntersection", "usePrevious", "useLayoutContext", "useTheme"] as const
1
+ export const HOOKS = ["useIntersection", "useLayoutContext", "usePrevious", "usePrint", "useTheme"] as const
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import { useState, createContext, useRef, useEffect } from "react"
3
+ import { useState, createContext, useRef, useEffect, useMemo, useCallback } from "react"
4
4
  import type { FC, PropsWithChildren } from "react"
5
5
  import type { LayoutContextProps } from "./LayoutContext.types"
6
6
 
@@ -20,6 +20,7 @@ const LayoutProvider: FC<LayoutProviderProps> = ({ children, breakpoint = 960 })
20
20
  const asideRef = useRef<HTMLElement | null>(null)
21
21
  const isMenuVisibleRef = useRef<boolean>(false)
22
22
  const windowWidthRef = useRef<number>(0)
23
+ const resizeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
23
24
 
24
25
  useEffect(() => {
25
26
  isMenuVisibleRef.current = isMenuVisible
@@ -67,11 +68,20 @@ const LayoutProvider: FC<LayoutProviderProps> = ({ children, breakpoint = 960 })
67
68
  }
68
69
  }
69
70
 
70
- globalThis.addEventListener("resize", handleResize)
71
+ const throttledResize = (): void => {
72
+ if (resizeTimerRef.current) return
73
+ resizeTimerRef.current = setTimeout(() => {
74
+ handleResize()
75
+ resizeTimerRef.current = null
76
+ }, 150)
77
+ }
78
+
79
+ globalThis.addEventListener("resize", throttledResize)
71
80
  globalThis.document.addEventListener("mousedown", handleClickOutside)
72
81
 
73
82
  return (): void => {
74
- globalThis.removeEventListener("resize", handleResize)
83
+ if (resizeTimerRef.current) globalThis.clearTimeout(resizeTimerRef.current)
84
+ globalThis.removeEventListener("resize", throttledResize)
75
85
  globalThis.document.removeEventListener("mousedown", handleClickOutside)
76
86
  }
77
87
  }, [breakpoint])
@@ -79,24 +89,24 @@ const LayoutProvider: FC<LayoutProviderProps> = ({ children, breakpoint = 960 })
79
89
  const isWideEnough: boolean = hasMounted ? windowWidth >= breakpoint : false
80
90
  const shouldShowNavigation: boolean = isWideEnough || isMenuVisible
81
91
 
82
- const onNavigationToggle = (): void => setIsMenuVisible((prev) => !prev)
83
- const onAsideClose = (): void => setIsMenuVisible(false)
84
-
85
- return (
86
- <LayoutContext.Provider
87
- value={{
88
- asideRef,
89
- hasMounted,
90
- isMenuVisible,
91
- isWideEnough,
92
- onAsideClose,
93
- onNavigationToggle,
94
- shouldShowNavigation,
95
- }}
96
- >
97
- {children}
98
- </LayoutContext.Provider>
92
+ const onNavigationToggle = useCallback((): void => setIsMenuVisible((prev) => !prev), [])
93
+ const onAsideClose = useCallback((): void => setIsMenuVisible(false), [])
94
+
95
+ const contextValue: LayoutContextProps = useMemo(
96
+ () => ({
97
+ asideRef,
98
+ hasMounted,
99
+ isMenuVisible,
100
+ isWideEnough,
101
+ onAsideClose,
102
+ onNavigationToggle,
103
+ shouldShowNavigation,
104
+ windowWidth,
105
+ }),
106
+ [hasMounted, isMenuVisible, isWideEnough, shouldShowNavigation, onAsideClose, onNavigationToggle, windowWidth],
99
107
  )
108
+
109
+ return <LayoutContext.Provider value={contextValue}>{children}</LayoutContext.Provider>
100
110
  }
101
111
 
102
112
  export { LayoutContext, LayoutProvider }
@@ -8,4 +8,5 @@ export interface LayoutContextProps {
8
8
  onAsideClose: () => void
9
9
  onNavigationToggle: () => void
10
10
  shouldShowNavigation: boolean
11
+ windowWidth: number
11
12
  }
@@ -2,8 +2,8 @@
2
2
 
3
3
  import { useState, useEffect } from "react"
4
4
 
5
- export const useDebounce = (value: unknown, delay: number): unknown => {
6
- const [debouncedValue, setDebouncedValue] = useState<unknown>(value)
5
+ export const useDebounce = <T,>(value: T, delay: number): T => {
6
+ const [debouncedValue, setDebouncedValue] = useState<T>(value)
7
7
 
8
8
  useEffect(() => {
9
9
  const handler: ReturnType<typeof setTimeout> = setTimeout(() => {
@@ -12,6 +12,7 @@ const DEFAULT_LAYOUT_CONTEXT: LayoutContextProps = {
12
12
  onAsideClose: () => {},
13
13
  onNavigationToggle: () => {},
14
14
  shouldShowNavigation: true,
15
+ windowWidth: 0,
15
16
  }
16
17
 
17
18
  export const useLayoutContext = (): LayoutContextProps => {
@@ -0,0 +1,31 @@
1
+ "use client"
2
+
3
+ import { useCallback, useEffect, useState } from "react"
4
+
5
+ interface UsePrintReturn {
6
+ isPrinting: boolean
7
+ print: () => void
8
+ }
9
+
10
+ export const usePrint = (): UsePrintReturn => {
11
+ const [isPrinting, setIsPrinting] = useState<boolean>(false)
12
+
13
+ useEffect(() => {
14
+ const handleBeforePrint = (): void => setIsPrinting(true)
15
+ const handleAfterPrint = (): void => setIsPrinting(false)
16
+
17
+ globalThis.addEventListener("beforeprint", handleBeforePrint)
18
+ globalThis.addEventListener("afterprint", handleAfterPrint)
19
+
20
+ return (): void => {
21
+ globalThis.removeEventListener("beforeprint", handleBeforePrint)
22
+ globalThis.removeEventListener("afterprint", handleAfterPrint)
23
+ }
24
+ }, [])
25
+
26
+ const print = useCallback((): void => {
27
+ globalThis.print()
28
+ }, [])
29
+
30
+ return { isPrinting, print }
31
+ }
package/index.ts CHANGED
@@ -1,3 +1,6 @@
1
+ // eslint-disable-next-line typescript/triple-slash-reference
2
+ /// <reference path="./globals.d.ts" />
3
+
1
4
  export { Alert } from "./components/Alert"
2
5
  export { Breadcrumb } from "./components/Breadcrumb"
3
6
  export { Browser } from "./components/Browser"
@@ -64,6 +67,7 @@ export type { TextBaseProps, TextProps } from "./components/Text"
64
67
  export { useIntersection } from "./hooks/useIntersection"
65
68
  export { useLayoutContext } from "./hooks/useLayoutContext"
66
69
  export { usePrevious } from "./hooks/usePrevious"
70
+ export { usePrint } from "./hooks/usePrint"
67
71
  export { useTheme } from "./hooks/useTheme"
68
72
 
69
73
  export type { HeliosAppearanceType, HeliosColorType } from "./types/colors"