@heliosgraphics/ui 2.0.0-alpha.94 → 2.0.0-alpha.96

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 (113) hide show
  1. package/components/Alert/Alert.tsx +2 -0
  2. package/components/Breadcrumb/Breadcrumb.tsx +17 -1
  3. package/components/Browser/Browser.tsx +2 -0
  4. package/components/Button/Button.tsx +11 -7
  5. package/components/ButtonGroup/ButtonGroup.tsx +2 -0
  6. package/components/Checkbox/Checkbox.tsx +66 -55
  7. package/components/Clock/Clock.tsx +23 -19
  8. package/components/Column/Column.tsx +2 -0
  9. package/components/Confirm/Confirm.tsx +2 -0
  10. package/components/DatePicker/DatePicker.meta.ts +12 -5
  11. package/components/DatePicker/DatePicker.module.css +70 -1
  12. package/components/DatePicker/DatePicker.tsx +116 -4
  13. package/components/DatePicker/DatePicker.types.ts +6 -1
  14. package/components/DatePicker/DatePicker.utils.ts +53 -0
  15. package/components/Debug/Debug.tsx +2 -0
  16. package/components/Details/Details.tsx +2 -0
  17. package/components/Dialog/Dialog.module.css +48 -40
  18. package/components/Dialog/Dialog.tsx +23 -58
  19. package/components/Donut/Donut.tsx +5 -6
  20. package/components/Dot/Dot.tsx +2 -0
  21. package/components/Dropdown/Dropdown.module.css +25 -10
  22. package/components/Dropdown/Dropdown.tsx +22 -29
  23. package/components/Fieldset/Fieldset.tsx +2 -0
  24. package/components/Flex/Flex.meta.ts +1 -0
  25. package/components/Flex/Flex.tsx +23 -2
  26. package/components/Flex/Flex.types.ts +1 -0
  27. package/components/Flex/Flex.utils.spec.ts +4 -1
  28. package/components/Flex/Flex.utils.ts +4 -1
  29. package/components/Grid/Grid.tsx +2 -0
  30. package/components/Heading/Heading.meta.ts +5 -0
  31. package/components/Heading/Heading.tsx +15 -9
  32. package/components/Heading/Heading.types.ts +1 -0
  33. package/components/Heading/components/H0/H0.tsx +2 -0
  34. package/components/Heading/components/H1/H1.tsx +4 -1
  35. package/components/Heading/components/H2/H2.tsx +4 -1
  36. package/components/Heading/components/H3/H3.tsx +4 -1
  37. package/components/Heading/components/H4/H4.tsx +4 -1
  38. package/components/Heading/components/H5/H5.tsx +4 -1
  39. package/components/Heading/components/H6/H6.tsx +4 -1
  40. package/components/Icon/Icon.tsx +2 -0
  41. package/components/Input/Input.tsx +103 -95
  42. package/components/Layout/Layout.tsx +2 -0
  43. package/components/Layout/components/LayoutAside/LayoutAside.tsx +2 -0
  44. package/components/Layout/components/LayoutAside/components/LayoutAsideContent/LayoutAsideContent.tsx +2 -0
  45. package/components/Layout/components/LayoutAside/components/LayoutAsideFooter/LayoutAsideFooter.tsx +2 -0
  46. package/components/Layout/components/LayoutAside/components/LayoutAsideToggle/LayoutAsideToggle.tsx +2 -0
  47. package/components/Layout/components/LayoutMain/LayoutMain.tsx +2 -0
  48. package/components/Layout/components/LayoutMain/components/LayoutMainContent/LayoutMainContent.tsx +2 -0
  49. package/components/Layout/components/LayoutNavigation/LayoutNavigation.tsx +2 -0
  50. package/components/Layout/components/LayoutSubNavigation/LayoutSubNavigation.tsx +2 -0
  51. package/components/Loading/Loading.tsx +2 -0
  52. package/components/Markdown/Markdown.tsx +2 -0
  53. package/components/Masonry/Masonry.tsx +5 -1
  54. package/components/Menu/Menu.tsx +2 -0
  55. package/components/Menu/components/MenuCategory/MenuCategory.tsx +2 -0
  56. package/components/Menu/components/MenuFilter/MenuFilter.tsx +2 -2
  57. package/components/Menu/components/MenuItem/MenuItem.tsx +2 -0
  58. package/components/Overlay/Overlay.module.css +42 -18
  59. package/components/Overlay/Overlay.tsx +26 -5
  60. package/components/Pie/Pie.tsx +2 -0
  61. package/components/Pill/Pill.meta.ts +9 -1
  62. package/components/Pill/Pill.module.css +11 -0
  63. package/components/Pill/Pill.tsx +30 -5
  64. package/components/Pill/Pill.types.ts +2 -0
  65. package/components/Placeholder/Placeholder.tsx +2 -0
  66. package/components/Progress/Progress.tsx +2 -0
  67. package/components/Radio/Radio.tsx +2 -0
  68. package/components/Range/Range.tsx +2 -0
  69. package/components/Segments/Segments.context.ts +19 -0
  70. package/components/Segments/Segments.meta.ts +4 -0
  71. package/components/Segments/Segments.tsx +34 -42
  72. package/components/Segments/Segments.types.ts +1 -0
  73. package/components/Segments/components/SegmentButton/SegmentButton.meta.ts +0 -4
  74. package/components/Segments/components/SegmentButton/SegmentButton.tsx +28 -2
  75. package/components/Segments/components/SegmentButton/SegmentButton.types.ts +0 -2
  76. package/components/Select/Select.tsx +40 -43
  77. package/components/Separator/Separator.tsx +2 -0
  78. package/components/Separator/components/HRMarkup/HRMarkup.tsx +2 -0
  79. package/components/Separator/components/HorizontalSeparator/HorizontalSeparator.tsx +2 -0
  80. package/components/Separator/components/VerticalSeparator/VerticalSeparator.tsx +2 -0
  81. package/components/Setup/Setup.tsx +3 -0
  82. package/components/Shimmer/Shimmer.tsx +2 -0
  83. package/components/Slider/Slider.tsx +2 -0
  84. package/components/Spacer/Spacer.tsx +2 -0
  85. package/components/Table/Table.tsx +2 -0
  86. package/components/Tabs/Tabs.meta.ts +12 -12
  87. package/components/Tabs/Tabs.module.css +25 -9
  88. package/components/Tabs/Tabs.tsx +50 -51
  89. package/components/Tabs/Tabs.types.ts +10 -3
  90. package/components/Text/Text.tsx +2 -0
  91. package/components/Text/components/Div/Div.tsx +4 -1
  92. package/components/Text/components/Micro/Micro.tsx +4 -1
  93. package/components/Text/components/P/P.tsx +4 -1
  94. package/components/Text/components/Small/Small.tsx +4 -1
  95. package/components/Text/components/Tiny/Tiny.tsx +4 -1
  96. package/components/Textarea/Textarea.tsx +14 -13
  97. package/components/Tile/Tile.tsx +2 -0
  98. package/components/Timestamp/Timestamp.tsx +2 -0
  99. package/components/Toggle/Toggle.tsx +2 -0
  100. package/components/Tooltip/Tooltip.module.css +13 -0
  101. package/components/Tooltip/Tooltip.tsx +19 -11
  102. package/components/Tooltip/Tooltip.types.ts +0 -1
  103. package/components/Tooltip/components/TooltipContent/TooltipContent.tsx +4 -0
  104. package/components/Tooltip/components/TooltipTrigger/TooltipTrigger.tsx +6 -2
  105. package/components/shared/InputLabel/InputLabel.tsx +2 -0
  106. package/components/shared/ResultList/ResultList.tsx +7 -5
  107. package/constants/components.ts +2 -2
  108. package/constants/meta.ts +9 -9
  109. package/contexts/LayoutContext/LayoutContext.tsx +15 -34
  110. package/contexts/LayoutContext/LayoutContext.types.ts +0 -1
  111. package/hooks/useLayoutContext.tsx +0 -1
  112. package/index.ts +5 -0
  113. package/package.json +4 -4
@@ -1,6 +1,9 @@
1
+ import { getClasses } from "@heliosgraphics/utils"
1
2
  import type { FC } from "react"
2
3
  import type { MicroProps } from "./Micro.types"
3
4
 
4
5
  export const Micro: FC<MicroProps> = (props) => {
5
- return <small {...props} className={`micro ${props.className}`} data-ui-component="Text.Micro" />
6
+ return <small {...props} className={getClasses("micro", props.className)} data-ui-component="Text.Micro" />
6
7
  }
8
+
9
+ Micro.displayName = "Micro"
@@ -1,6 +1,9 @@
1
+ import { getClasses } from "@heliosgraphics/utils"
1
2
  import type { FC } from "react"
2
3
  import type { PProps } from "./P.types"
3
4
 
4
5
  export const P: FC<PProps> = (props) => {
5
- return <p {...props} className={`p ${props.className}`} data-ui-component="Text.P" />
6
+ return <p {...props} className={getClasses("p", props.className)} data-ui-component="Text.P" />
6
7
  }
8
+
9
+ P.displayName = "P"
@@ -1,6 +1,9 @@
1
+ import { getClasses } from "@heliosgraphics/utils"
1
2
  import type { FC } from "react"
2
3
  import type { SmallProps } from "./Small.types"
3
4
 
4
5
  export const Small: FC<SmallProps> = (props) => {
5
- return <small {...props} className={`small ${props.className}`} data-ui-component="Text.Small" />
6
+ return <small {...props} className={getClasses("small", props.className)} data-ui-component="Text.Small" />
6
7
  }
8
+
9
+ Small.displayName = "Small"
@@ -1,6 +1,9 @@
1
+ import { getClasses } from "@heliosgraphics/utils"
1
2
  import type { FC } from "react"
2
3
  import type { TinyProps } from "./Tiny.types"
3
4
 
4
5
  export const Tiny: FC<TinyProps> = (props) => {
5
- return <small {...props} className={`tiny ${props.className}`} data-ui-component="Text.Tiny" />
6
+ return <small {...props} className={getClasses("tiny", props.className)} data-ui-component="Text.Tiny" />
6
7
  }
8
+
9
+ Tiny.displayName = "Tiny"
@@ -2,16 +2,17 @@
2
2
 
3
3
  import { getClasses } from "@heliosgraphics/utils"
4
4
  import { Text } from "../Text"
5
- import { useId, useRef, useEffect } from "react"
5
+ import { useId, useRef, useEffect, useImperativeHandle, forwardRef } from "react"
6
6
  import { InputLabel } from "../shared/InputLabel"
7
7
  import styles from "./Textarea.module.css"
8
- import type { FC } from "react"
9
8
  import type { TextareaProps } from "./Textarea.types"
10
9
 
11
- export const Textarea: FC<TextareaProps> = (props) => {
12
- const textareaRef = useRef<HTMLTextAreaElement | null>(null)
10
+ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>((props, ref) => {
11
+ const internalRef = useRef<HTMLTextAreaElement | null>(null)
13
12
  const { autoComplete: _autoComplete, helperText: _helperText, isDisabled, isLabelHidden, ...goodProps } = props
14
13
 
14
+ useImperativeHandle(ref, () => internalRef.current as HTMLTextAreaElement)
15
+
15
16
  const htmlFor: string = useId()
16
17
  const helperId: string = `${htmlFor}-helper`
17
18
  const textareaClasses: string = getClasses(styles.textarea, "flex flex-column", {
@@ -19,16 +20,14 @@ export const Textarea: FC<TextareaProps> = (props) => {
19
20
  })
20
21
 
21
22
  const onInput = (): void => {
22
- if (textareaRef?.current) {
23
- textareaRef.current.style.height = "auto"
24
- textareaRef.current.style.height = textareaRef.current.scrollHeight + "px"
25
- }
23
+ if (!internalRef.current) return
24
+
25
+ internalRef.current.style.height = "auto"
26
+ internalRef.current.style.height = internalRef.current.scrollHeight + "px"
26
27
  }
27
28
 
28
29
  useEffect(() => {
29
- if (textareaRef?.current) {
30
- onInput()
31
- }
30
+ onInput()
32
31
 
33
32
  const timer: ReturnType<typeof setTimeout> = setTimeout(() => onInput(), 100)
34
33
 
@@ -49,7 +48,7 @@ export const Textarea: FC<TextareaProps> = (props) => {
49
48
  )}
50
49
  <textarea
51
50
  {...goodProps}
52
- ref={textareaRef}
51
+ ref={internalRef}
53
52
  id={htmlFor}
54
53
  rows={goodProps.rows ?? 4}
55
54
  onInput={onInput}
@@ -64,4 +63,6 @@ export const Textarea: FC<TextareaProps> = (props) => {
64
63
  )}
65
64
  </div>
66
65
  )
67
- }
66
+ })
67
+
68
+ Textarea.displayName = "Textarea"
@@ -66,3 +66,5 @@ export const Tile: FC<TileProps> = ({
66
66
  </Flex>
67
67
  )
68
68
  }
69
+
70
+ Tile.displayName = "Tile"
@@ -17,3 +17,5 @@ export const Timestamp: FC<TimestampProps> = ({ date, fromNow, text, format }) =
17
17
  </time>
18
18
  )
19
19
  }
20
+
21
+ Timestamp.displayName = "Timestamp"
@@ -58,3 +58,5 @@ export const Toggle: FC<ToggleProps> = ({
58
58
  </div>
59
59
  )
60
60
  }
61
+
62
+ Toggle.displayName = "Toggle"
@@ -19,6 +19,19 @@
19
19
  border-radius: var(--radius-sm);
20
20
 
21
21
  position-area: bottom center;
22
+
23
+ opacity: 1;
24
+ transition:
25
+ opacity 150ms ease-out,
26
+ display 150ms ease-out allow-discrete;
27
+
28
+ @starting-style {
29
+ opacity: 0;
30
+ }
31
+
32
+ &:not(:popover-open) {
33
+ opacity: 0;
34
+ }
22
35
  }
23
36
 
24
37
  .tooltipBottomLeft {
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import { useRef, useEffect, useMemo, useState, useId, type FC } from "react"
3
+ import { useRef, useEffect, useMemo, useId, type FC } from "react"
4
4
  import { getClasses } from "@heliosgraphics/utils"
5
5
  import styles from "./Tooltip.module.css"
6
6
  import type { TooltipProps, TooltipComposition, TooltipContextType } from "./Tooltip.types"
@@ -8,7 +8,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> = {
11
+ const POSITION_CLASS_MAP = {
12
12
  "bottom-center": undefined,
13
13
  "bottom-left": styles.tooltipBottomLeft,
14
14
  "bottom-right": styles.tooltipBottomRight,
@@ -17,10 +17,9 @@ const POSITION_CLASS_MAP: Record<NonNullable<TooltipProps["position"]>, string |
17
17
  "top-right": styles.tooltipTopRight,
18
18
  "left-center": styles.tooltipLeftCenter,
19
19
  "right-center": styles.tooltipRightCenter,
20
- }
20
+ } as const satisfies Record<NonNullable<TooltipProps["position"]>, string | undefined>
21
21
 
22
22
  const TooltipComponent: FC<TooltipProps> = ({ children = null, position = "bottom-center", isVisible = false }) => {
23
- const [isOpen, setIsOpen] = useState<boolean>(isVisible)
24
23
  const triggerRef = useRef<HTMLDivElement | null>(null)
25
24
  const popoverRef = useRef<HTMLDivElement | null>(null)
26
25
  const id: string = useId()
@@ -45,27 +44,35 @@ const TooltipComponent: FC<TooltipProps> = ({ children = null, position = "botto
45
44
  return
46
45
  }
47
46
 
47
+ const setAriaDescribedBy = (isOpen: boolean): void => {
48
+ if (isOpen) {
49
+ trigger.setAttribute("aria-describedby", tooltipId)
50
+ } else {
51
+ trigger.removeAttribute("aria-describedby")
52
+ }
53
+ }
54
+
48
55
  if (isVisible) {
49
56
  popover.showPopover()
50
- setIsOpen(true)
57
+ setAriaDescribedBy(true)
51
58
  }
52
59
 
53
60
  const handleMouseEnter = (): void => {
54
61
  popover.showPopover()
55
- setIsOpen(true)
62
+ setAriaDescribedBy(true)
56
63
  }
57
64
 
58
65
  const handleMouseLeave = (): void => {
59
66
  if (!isVisible) {
60
67
  popover.hidePopover()
61
- setIsOpen(false)
68
+ setAriaDescribedBy(false)
62
69
  }
63
70
  }
64
71
 
65
72
  const handleKeyDown = (event: KeyboardEvent): void => {
66
73
  if (event.key === "Escape") {
67
74
  popover.hidePopover()
68
- setIsOpen(false)
75
+ setAriaDescribedBy(false)
69
76
  }
70
77
  }
71
78
 
@@ -78,7 +85,7 @@ const TooltipComponent: FC<TooltipProps> = ({ children = null, position = "botto
78
85
  trigger.removeEventListener("mouseleave", handleMouseLeave)
79
86
  trigger.removeEventListener("keydown", handleKeyDown)
80
87
  }
81
- }, [isVisible])
88
+ }, [isVisible, tooltipId])
82
89
 
83
90
  const contextValue: TooltipContextType = useMemo(
84
91
  () => ({
@@ -87,9 +94,8 @@ const TooltipComponent: FC<TooltipProps> = ({ children = null, position = "botto
87
94
  tooltipClasses,
88
95
  anchorName,
89
96
  tooltipId,
90
- isOpen,
91
97
  }),
92
- [tooltipClasses, anchorName, tooltipId, isOpen],
98
+ [tooltipClasses, anchorName, tooltipId],
93
99
  )
94
100
 
95
101
  return (
@@ -105,3 +111,5 @@ export const Tooltip: FC<TooltipProps> & TooltipComposition = Object.assign(Tool
105
111
  Trigger: TooltipTrigger,
106
112
  Content: TooltipContent,
107
113
  })
114
+
115
+ Tooltip.displayName = "Tooltip"
@@ -23,7 +23,6 @@ export interface TooltipContextType {
23
23
  tooltipClasses: string
24
24
  anchorName: string
25
25
  tooltipId: string
26
- isOpen: boolean
27
26
  }
28
27
 
29
28
  export interface TooltipComposition {
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import { Text } from "../../../Text"
2
4
  import { useTooltipContext } from "../../Tooltip.utils"
3
5
  import type { CSSProperties, FC } from "react"
@@ -21,3 +23,5 @@ export const TooltipContent: FC<TooltipContentProps> = ({ children }) => {
21
23
  </div>
22
24
  )
23
25
  }
26
+
27
+ TooltipContent.displayName = "TooltipContent"
@@ -1,15 +1,19 @@
1
+ "use client"
2
+
1
3
  import type { TooltipTriggerProps } from "./TooltipTrigger.types"
2
4
  import { useTooltipContext } from "../../Tooltip.utils"
3
5
  import type { CSSProperties, FC } from "react"
4
6
 
5
7
  export const TooltipTrigger: FC<TooltipTriggerProps> = ({ children }) => {
6
- const { triggerRef, anchorName, tooltipId, isOpen } = useTooltipContext()
8
+ const { triggerRef, anchorName } = useTooltipContext()
7
9
 
8
10
  const triggerStyle: CSSProperties = { anchorName } as CSSProperties
9
11
 
10
12
  return (
11
- <div ref={triggerRef} style={triggerStyle} aria-describedby={isOpen ? tooltipId : undefined}>
13
+ <div ref={triggerRef} style={triggerStyle}>
12
14
  {children}
13
15
  </div>
14
16
  )
15
17
  }
18
+
19
+ TooltipTrigger.displayName = "TooltipTrigger"
@@ -16,3 +16,5 @@ export const InputLabel: FC<InputLabelProps> = ({ id, label, isDisabled, isHidde
16
16
  </label>
17
17
  )
18
18
  }
19
+
20
+ InputLabel.displayName = "InputLabel"
@@ -4,10 +4,10 @@ import { Flex } from "../../Flex"
4
4
  import { Icon } from "../../Icon"
5
5
  import { Text } from "../../Text"
6
6
  import styles from "./ResultList.module.css"
7
- import type { ChangeEvent } from "react"
7
+ import type { ChangeEventHandler, FC } from "react"
8
8
  import type { ResultListProps } from "./ResultList.types"
9
9
 
10
- export const ResultList = ({ items, isHidden, ref }: ResultListProps) => {
10
+ export const ResultList: FC<ResultListProps> = ({ items, isHidden, ref }) => {
11
11
  if (!items?.length || isHidden) return null
12
12
 
13
13
  const resultListClasses: string = getClasses(styles.resultList, "elevation-lg")
@@ -24,9 +24,9 @@ export const ResultList = ({ items, isHidden, ref }: ResultListProps) => {
24
24
  if (item.type === "separator") return <li key={itemKey} className={styles.resultList__separator} />
25
25
 
26
26
  if (item.type === "checkbox") {
27
- const onCheck = (event: ChangeEvent<HTMLInputElement>): void =>
28
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
- item.onClick?.(event as any)
27
+ const onCheck: ChangeEventHandler<HTMLInputElement> = (): void => {
28
+ item.onClick?.({} as React.MouseEvent)
29
+ }
30
30
 
31
31
  return (
32
32
  <li key={itemKey} onClick={item.onClick} className={itemClasses}>
@@ -63,3 +63,5 @@ export const ResultList = ({ items, isHidden, ref }: ResultListProps) => {
63
63
  </ol>
64
64
  )
65
65
  }
66
+
67
+ ResultList.displayName = "ResultList"
@@ -49,7 +49,7 @@ import { meta as metaTimestamp } from "../components/Timestamp/Timestamp.meta"
49
49
  import { meta as metaToggle } from "../components/Toggle/Toggle.meta"
50
50
  import { meta as metaTooltip } from "../components/Tooltip/Tooltip.meta"
51
51
 
52
- export const COMPONENTS: Record<string, HeliosAttributeMeta<unknown>> = {
52
+ export const COMPONENTS = {
53
53
  Alert: metaAlert,
54
54
  Breadcrumb: metaBreadcrumb,
55
55
  Browser: metaBrowser,
@@ -98,4 +98,4 @@ export const COMPONENTS: Record<string, HeliosAttributeMeta<unknown>> = {
98
98
  Timestamp: metaTimestamp,
99
99
  Toggle: metaToggle,
100
100
  Tooltip: metaTooltip,
101
- }
101
+ } as const satisfies Record<string, HeliosAttributeMeta<unknown>>
package/constants/meta.ts CHANGED
@@ -3,34 +3,34 @@ import type { HeliosComponentStatusType, HeliosComponentCategoryType } from "../
3
3
  import type { HeliosColorType } from "../types/colors"
4
4
  import type { HeliosIconType } from "../types/icons"
5
5
 
6
- export const STATUS_COLORS: Record<HeliosComponentStatusType, HeliosColorType> = {
6
+ export const STATUS_COLORS = {
7
7
  experimental: "pink",
8
8
  nominal: "gray",
9
9
  stable: "green",
10
10
  internal: "gray",
11
- }
11
+ } as const satisfies Record<HeliosComponentStatusType, HeliosColorType>
12
12
 
13
- export const STATUS_ICONS: Record<HeliosComponentStatusType, HeliosIconType> = {
13
+ export const STATUS_ICONS = {
14
14
  experimental: "bolt",
15
15
  nominal: "bolt",
16
16
  stable: "check",
17
17
  internal: "bullseye",
18
- }
18
+ } as const satisfies Record<HeliosComponentStatusType, HeliosIconType>
19
19
 
20
- export const STATUS_NAMES: Record<HeliosComponentStatusType, string> = {
20
+ export const STATUS_NAMES = {
21
21
  experimental: "Experimental",
22
22
  nominal: "Might Change",
23
23
  stable: "Stable",
24
24
  internal: "Internal",
25
- }
25
+ } as const satisfies Record<HeliosComponentStatusType, string>
26
26
 
27
- export const TYPE_NAMES: Record<HeliosComponentCategoryType, string> = {
27
+ export const TYPE_NAMES = {
28
28
  content: "Content",
29
29
  pattern: "Pattern",
30
30
  core: "Core",
31
31
  layout: "Layout",
32
32
  meta: "Meta",
33
- }
33
+ } as const satisfies Record<HeliosComponentCategoryType, string>
34
34
 
35
35
  interface StatusReturnType {
36
36
  color: HeliosColorType
@@ -40,7 +40,7 @@ interface StatusReturnType {
40
40
  }
41
41
 
42
42
  export const getStatus = (component: string = "Example"): StatusReturnType => {
43
- const componentMeta = COMPONENTS[component]
43
+ const componentMeta = COMPONENTS[component as keyof typeof COMPONENTS]
44
44
 
45
45
  if (!componentMeta || !componentMeta._status) {
46
46
  throw new Error(`Component "${component}" not found or missing _status`)
@@ -15,44 +15,37 @@ type LayoutProviderProps = PropsWithChildren<LayoutProviderBaseProps>
15
15
 
16
16
  const LayoutProvider: FC<LayoutProviderProps> = ({ children, breakpoint = 960 }) => {
17
17
  const [isMenuVisible, setIsMenuVisible] = useState<boolean>(false)
18
- const [windowWidth, setWindowWidth] = useState<number>(0)
18
+ const [isWideEnough, setIsWideEnough] = useState<boolean>(false)
19
19
  const [hasMounted, setHasMounted] = useState<boolean>(false)
20
20
  const asideRef = useRef<HTMLElement | null>(null)
21
21
  const isMenuVisibleRef = useRef<boolean>(false)
22
- const windowWidthRef = useRef<number>(0)
23
- const resizeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
24
22
 
25
23
  useEffect(() => {
26
24
  isMenuVisibleRef.current = isMenuVisible
27
25
  }, [isMenuVisible])
28
26
 
29
27
  useEffect(() => {
30
- const initialWidth = globalThis.innerWidth
31
-
32
28
  setHasMounted(true)
33
- setWindowWidth(initialWidth)
34
- windowWidthRef.current = initialWidth
35
-
36
- const initialMenuVisible = initialWidth >= breakpoint
37
29
 
38
- setIsMenuVisible(initialMenuVisible)
39
- isMenuVisibleRef.current = initialMenuVisible
30
+ const mql = globalThis.matchMedia(`(min-width: ${breakpoint}px)`)
40
31
 
41
- const handleResize = (): void => {
42
- const newWidth: number = globalThis.innerWidth
43
- const wasWideEnough: boolean = windowWidthRef.current >= breakpoint
44
- const isNowWideEnough: boolean = newWidth >= breakpoint
32
+ const handleChange = (event: MediaQueryListEvent | MediaQueryList): void => {
33
+ const matches = event.matches
45
34
 
46
- setWindowWidth(newWidth)
47
- windowWidthRef.current = newWidth
35
+ setIsWideEnough(matches)
48
36
 
49
- if (isNowWideEnough && !wasWideEnough) {
37
+ if (matches) {
50
38
  setIsMenuVisible(true)
51
- } else if (!isNowWideEnough && wasWideEnough) {
39
+ } else {
52
40
  setIsMenuVisible(false)
53
41
  }
54
42
  }
55
43
 
44
+ // Set initial state
45
+ handleChange(mql)
46
+
47
+ mql.addEventListener("change", handleChange)
48
+
56
49
  const handleClickOutside = (event: MouseEvent): void => {
57
50
  if (asideRef?.current?.contains(event.target as Node)) return
58
51
 
@@ -60,7 +53,7 @@ const LayoutProvider: FC<LayoutProviderProps> = ({ children, breakpoint = 960 })
60
53
  const shouldIgnore: Element | null = clickedElement?.closest(`[${IGNORE_DATA_ATTRIBUTE}]`)
61
54
 
62
55
  if (!shouldIgnore) {
63
- const currentIsWideEnough: boolean = globalThis.innerWidth >= breakpoint
56
+ const currentIsWideEnough: boolean = mql.matches
64
57
 
65
58
  if (!currentIsWideEnough && isMenuVisibleRef.current) {
66
59
  setIsMenuVisible(false)
@@ -68,25 +61,14 @@ const LayoutProvider: FC<LayoutProviderProps> = ({ children, breakpoint = 960 })
68
61
  }
69
62
  }
70
63
 
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)
80
64
  globalThis.document.addEventListener("mousedown", handleClickOutside)
81
65
 
82
66
  return (): void => {
83
- if (resizeTimerRef.current) globalThis.clearTimeout(resizeTimerRef.current)
84
- globalThis.removeEventListener("resize", throttledResize)
67
+ mql.removeEventListener("change", handleChange)
85
68
  globalThis.document.removeEventListener("mousedown", handleClickOutside)
86
69
  }
87
70
  }, [breakpoint])
88
71
 
89
- const isWideEnough: boolean = hasMounted ? windowWidth >= breakpoint : false
90
72
  const shouldShowNavigation: boolean = isWideEnough || isMenuVisible
91
73
 
92
74
  const onNavigationToggle = useCallback((): void => setIsMenuVisible((prev) => !prev), [])
@@ -101,9 +83,8 @@ const LayoutProvider: FC<LayoutProviderProps> = ({ children, breakpoint = 960 })
101
83
  onAsideClose,
102
84
  onNavigationToggle,
103
85
  shouldShowNavigation,
104
- windowWidth,
105
86
  }),
106
- [hasMounted, isMenuVisible, isWideEnough, shouldShowNavigation, onAsideClose, onNavigationToggle, windowWidth],
87
+ [hasMounted, isMenuVisible, isWideEnough, shouldShowNavigation, onAsideClose, onNavigationToggle],
107
88
  )
108
89
 
109
90
  return <LayoutContext.Provider value={contextValue}>{children}</LayoutContext.Provider>
@@ -8,5 +8,4 @@ export interface LayoutContextProps {
8
8
  onAsideClose: () => void
9
9
  onNavigationToggle: () => void
10
10
  shouldShowNavigation: boolean
11
- windowWidth: number
12
11
  }
@@ -12,7 +12,6 @@ const DEFAULT_LAYOUT_CONTEXT: LayoutContextProps = {
12
12
  onAsideClose: () => {},
13
13
  onNavigationToggle: () => {},
14
14
  shouldShowNavigation: true,
15
- windowWidth: 0,
16
15
  }
17
16
 
18
17
  export const useLayoutContext = (): LayoutContextProps => {
package/index.ts CHANGED
@@ -65,10 +65,15 @@ export type { ResultItem } from "./components/shared/ResultList"
65
65
  export type { SelectItem } from "./components/Select"
66
66
  export type { TextBaseProps, TextProps } from "./components/Text"
67
67
 
68
+ export { useChatScroll } from "./hooks/useChatScroll"
69
+ export { useDebounce } from "./hooks/useDebounce"
68
70
  export { useIntersection } from "./hooks/useIntersection"
71
+ export { useInterval } from "./hooks/useInterval"
72
+ export { useKeyPress } from "./hooks/useKeyPress"
69
73
  export { useLayoutContext } from "./hooks/useLayoutContext"
70
74
  export { usePrevious } from "./hooks/usePrevious"
71
75
  export { usePrint } from "./hooks/usePrint"
76
+ export { useResizeObserver } from "./hooks/useResizeObserver"
72
77
  export { useTheme } from "./hooks/useTheme"
73
78
 
74
79
  export type { HeliosAppearanceType, HeliosColorType } from "./types/colors"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heliosgraphics/ui",
3
- "version": "2.0.0-alpha.94",
3
+ "version": "2.0.0-alpha.96",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "*.css",
@@ -45,7 +45,7 @@
45
45
  "@heliosgraphics/css": "^1.0.0-alpha.5",
46
46
  "@heliosgraphics/icons": "^1.0.0-alpha.11",
47
47
  "@heliosgraphics/utils": "^6.0.0-alpha.9",
48
- "marked": "^17.0.2",
48
+ "marked": "^17.0.3",
49
49
  "marked-linkify-it": "^3.1.14",
50
50
  "marked-xhtml": "^1.0.14"
51
51
  },
@@ -56,8 +56,8 @@
56
56
  "@vitest/coverage-v8": "^4.0.18",
57
57
  "esbuild": "^0.27.3",
58
58
  "esbuild-css-modules-plugin": "^3.1.5",
59
- "glob": "^13.0.3",
60
- "jsdom": "^28.0.0",
59
+ "glob": "^13.0.6",
60
+ "jsdom": "^28.1.0",
61
61
  "next": "^16.1.6",
62
62
  "prettier": "^3.8.1",
63
63
  "typescript": "^5.9.3",