@heliosgraphics/ui 2.0.0-alpha.95 → 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 (109) 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 +7 -3
  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 +4 -6
  18. package/components/Dialog/Dialog.tsx +25 -16
  19. package/components/Donut/Donut.tsx +2 -0
  20. package/components/Dot/Dot.tsx +2 -0
  21. package/components/Dropdown/Dropdown.module.css +5 -0
  22. package/components/Dropdown/Dropdown.tsx +21 -26
  23. package/components/Fieldset/Fieldset.tsx +2 -0
  24. package/components/Flex/Flex.meta.ts +1 -0
  25. package/components/Flex/Flex.tsx +22 -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 +2 -0
  35. package/components/Heading/components/H2/H2.tsx +2 -0
  36. package/components/Heading/components/H3/H3.tsx +2 -0
  37. package/components/Heading/components/H4/H4.tsx +2 -0
  38. package/components/Heading/components/H5/H5.tsx +2 -0
  39. package/components/Heading/components/H6/H6.tsx +2 -0
  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 -0
  57. package/components/Menu/components/MenuItem/MenuItem.tsx +2 -0
  58. package/components/Overlay/Overlay.tsx +18 -2
  59. package/components/Pie/Pie.tsx +2 -0
  60. package/components/Pill/Pill.meta.ts +9 -1
  61. package/components/Pill/Pill.module.css +11 -0
  62. package/components/Pill/Pill.tsx +28 -3
  63. package/components/Pill/Pill.types.ts +2 -0
  64. package/components/Placeholder/Placeholder.tsx +2 -0
  65. package/components/Progress/Progress.tsx +2 -0
  66. package/components/Radio/Radio.tsx +2 -0
  67. package/components/Range/Range.tsx +2 -0
  68. package/components/Segments/Segments.context.ts +19 -0
  69. package/components/Segments/Segments.meta.ts +4 -0
  70. package/components/Segments/Segments.tsx +34 -42
  71. package/components/Segments/Segments.types.ts +1 -0
  72. package/components/Segments/components/SegmentButton/SegmentButton.meta.ts +0 -4
  73. package/components/Segments/components/SegmentButton/SegmentButton.tsx +28 -3
  74. package/components/Segments/components/SegmentButton/SegmentButton.types.ts +0 -2
  75. package/components/Select/Select.tsx +40 -43
  76. package/components/Separator/Separator.tsx +2 -0
  77. package/components/Separator/components/HRMarkup/HRMarkup.tsx +2 -0
  78. package/components/Separator/components/HorizontalSeparator/HorizontalSeparator.tsx +2 -0
  79. package/components/Separator/components/VerticalSeparator/VerticalSeparator.tsx +2 -0
  80. package/components/Setup/Setup.tsx +3 -0
  81. package/components/Shimmer/Shimmer.tsx +2 -0
  82. package/components/Slider/Slider.tsx +2 -0
  83. package/components/Spacer/Spacer.tsx +2 -0
  84. package/components/Table/Table.tsx +2 -0
  85. package/components/Tabs/Tabs.meta.ts +12 -12
  86. package/components/Tabs/Tabs.module.css +25 -9
  87. package/components/Tabs/Tabs.tsx +49 -53
  88. package/components/Tabs/Tabs.types.ts +10 -3
  89. package/components/Text/Text.tsx +2 -0
  90. package/components/Text/components/Div/Div.tsx +2 -0
  91. package/components/Text/components/Micro/Micro.tsx +2 -0
  92. package/components/Text/components/P/P.tsx +2 -0
  93. package/components/Text/components/Small/Small.tsx +2 -0
  94. package/components/Text/components/Tiny/Tiny.tsx +2 -0
  95. package/components/Textarea/Textarea.tsx +14 -13
  96. package/components/Tile/Tile.tsx +2 -0
  97. package/components/Timestamp/Timestamp.tsx +2 -0
  98. package/components/Toggle/Toggle.tsx +2 -0
  99. package/components/Tooltip/Tooltip.tsx +17 -9
  100. package/components/Tooltip/Tooltip.types.ts +0 -1
  101. package/components/Tooltip/components/TooltipContent/TooltipContent.tsx +2 -0
  102. package/components/Tooltip/components/TooltipTrigger/TooltipTrigger.tsx +4 -2
  103. package/components/shared/InputLabel/InputLabel.tsx +2 -0
  104. package/components/shared/ResultList/ResultList.tsx +6 -4
  105. package/contexts/LayoutContext/LayoutContext.tsx +15 -34
  106. package/contexts/LayoutContext/LayoutContext.types.ts +0 -1
  107. package/hooks/useLayoutContext.tsx +0 -1
  108. package/index.ts +5 -0
  109. package/package.json +1 -1
@@ -18,3 +18,5 @@ export const VerticalSeparator: FC<VerticalSeparatorProps> = ({ height, classNam
18
18
 
19
19
  return <HRMarkup style={verticalStyle} className={hrClassNames} aria-orientation="vertical" />
20
20
  }
21
+
22
+ VerticalSeparator.displayName = "VerticalSeparator"
@@ -41,6 +41,7 @@ export const Setup: FC<SetupProps> = ({ theme = "system", fixedTheme }) => {
41
41
  <link key={`stylesheet-${linkProps.id}`} {...linkProps} />
42
42
  ))}
43
43
  <script
44
+ data-ui-component="Setup"
44
45
  dangerouslySetInnerHTML={{
45
46
  __html: `(${code.toString()})("${theme}", ${fixedThemeArg});`,
46
47
  }}
@@ -48,3 +49,5 @@ export const Setup: FC<SetupProps> = ({ theme = "system", fixedTheme }) => {
48
49
  </>
49
50
  )
50
51
  }
52
+
53
+ Setup.displayName = "Setup"
@@ -27,3 +27,5 @@ export const Shimmer: FC<ShimmerProps> = ({ isRounded, paddingTop, paddingBottom
27
27
  </div>
28
28
  )
29
29
  }
30
+
31
+ Shimmer.displayName = "Shimmer"
@@ -14,3 +14,5 @@ export const Slider: FC<SliderProps> = ({ "aria-label": ariaLabel, items }) => {
14
14
  </ul>
15
15
  )
16
16
  }
17
+
18
+ Slider.displayName = "Slider"
@@ -4,3 +4,5 @@ import type { SpacerProps } from "./Spacer.types"
4
4
  export const Spacer: FC<SpacerProps> = ({ gap }) => {
5
5
  return <div style={{ height: `${gap ?? 0}px` }} data-ui-component="Spacer" />
6
6
  }
7
+
8
+ Spacer.displayName = "Spacer"
@@ -17,3 +17,5 @@ export const Table: FC<TableProps> = ({ children, hasBorder, isMonoHeader }) =>
17
17
  </div>
18
18
  )
19
19
  }
20
+
21
+ Table.displayName = "Table"
@@ -2,23 +2,23 @@ import type { HeliosAttributeMeta } from "../../types/meta"
2
2
  import type { TabsProps } from "./Tabs.types"
3
3
 
4
4
  export const meta: HeliosAttributeMeta<TabsProps> = {
5
+ _extends: [],
5
6
  _patterns: [
6
7
  {
7
8
  id: "ui-tabs-default",
8
9
  description: "default",
9
- content: `<Tabs items={TAB_ITEMS} sections={TAB_SECTIONS}/>`,
10
+ content:
11
+ '<Tabs items={[{ name: "Tab 1", isActive: true, onClick: () => {} }, { name: "Tab 2", onClick: () => {} }]}>{CHILDREN}</Tabs>',
12
+ },
13
+ {
14
+ id: "ui-tabs-with-href",
15
+ description: "with links",
16
+ content:
17
+ '<Tabs items={[{ name: "Overview", href: "/overview", isActive: true }, { name: "Settings", href: "/settings" }]} />',
10
18
  },
11
19
  ],
12
20
  _status: "experimental",
13
- _category: "layout",
14
- active: {
15
- type: "number",
16
- isOptional: true,
17
- },
18
- items: {
19
- type: "Array<string>",
20
- },
21
- sections: {
22
- type: "Array<ReactNode>",
23
- },
21
+ _category: "core",
22
+ children: { type: "ReactNode", isOptional: true },
23
+ items: { type: "Array<TabItem>", description: "Tab items with name, onClick, href, isActive, and isDisabled" },
24
24
  }
@@ -1,27 +1,43 @@
1
- .tabs__ol {
1
+ .tabs__list {
2
2
  position: relative;
3
+ display: flex;
4
+ gap: 2px;
3
5
 
4
- height: 36px;
6
+ height: 40px;
5
7
  padding: 0 8px 0 0;
6
8
 
7
9
  box-shadow: inset 0 -1px 0 0 var(--ui-border-secondary);
8
-
9
- line-height: 36px;
10
+ line-height: 40px;
10
11
  }
11
12
 
12
- .tabs__ol__item {
13
+ .tabs__item {
14
+ display: flex;
15
+ align-items: center;
13
16
  padding: 0 12px;
14
17
 
15
18
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
16
- border: 1px solid var(--ui-border-secondary);
19
+ color: var(--ui-text-secondary);
20
+ text-decoration: none;
17
21
 
18
22
  cursor: pointer;
23
+ user-select: none;
24
+ }
25
+
26
+ .tabs__item:hover {
27
+ color: var(--ui-text-primary);
28
+ }
29
+
30
+ .tabs__itemActive {
31
+ color: var(--ui-text-primary);
32
+ box-shadow: inset 0 -2px 0 0 var(--ui-text-primary);
19
33
  }
20
34
 
21
- .tabs__ol__itemActive {
22
- border-bottom-color: var(--ui-bg-secondary);
35
+ .tabs__itemDisabled {
36
+ opacity: 0.4;
37
+ cursor: not-allowed;
38
+ pointer-events: none;
23
39
  }
24
40
 
25
- .tabs__section {
41
+ .tabs__content {
26
42
  padding: 24px 0;
27
43
  }
@@ -1,69 +1,65 @@
1
1
  "use client"
2
2
 
3
3
  import { getClasses } from "@heliosgraphics/utils"
4
- import { useId, useRef, useState, type FC, type KeyboardEvent } from "react"
5
- import type { TabsProps } from "./Tabs.types"
6
- import styles from "./Tabs.module.css"
7
4
  import { Text } from "../Text"
5
+ import styles from "./Tabs.module.css"
6
+ import type { FC, KeyboardEvent } from "react"
7
+ import type { TabItem, TabsProps } from "./Tabs.types"
8
8
 
9
- export const Tabs: FC<TabsProps> = ({ active: activeNumber, items, sections }) => {
10
- const [active, setActive] = useState(activeNumber || 0)
11
- const tabsId: string = useId()
12
- const tabRefs = useRef<(HTMLDivElement | null)[]>([])
13
-
14
- if (!items || !sections) return null
15
-
16
- const tabListClasses: string = getClasses(styles.tabs__ol, "flex gap-2")
17
-
18
- const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>, index: number): void => {
19
- let nextIndex: number | null = null
9
+ export const Tabs: FC<TabsProps> = ({ items, children }) => {
10
+ if (!items?.length) return null
20
11
 
21
- if (event.key === "ArrowRight") {
22
- event.preventDefault()
23
- nextIndex = (index + 1) % items.length
24
- } else if (event.key === "ArrowLeft") {
12
+ const onKeyDown = (event: KeyboardEvent<HTMLElement>, item: TabItem): void => {
13
+ if (event.key === " " || event.key === "Enter") {
25
14
  event.preventDefault()
26
- nextIndex = (index - 1 + items.length) % items.length
27
- }
28
-
29
- if (nextIndex !== null) {
30
- setActive(nextIndex)
31
- tabRefs.current[nextIndex]?.focus()
15
+ item.onClick?.()
32
16
  }
33
17
  }
34
18
 
35
19
  return (
36
20
  <div data-ui-component="Tabs">
37
- <div role="tablist" className={tabListClasses}>
38
- {items.map((tab, index) => (
39
- <div
40
- key={tab}
41
- ref={(el) => {
42
- tabRefs.current[index] = el
43
- }}
44
- role="tab"
45
- tabIndex={active === index ? 0 : -1}
46
- aria-selected={active === index}
47
- aria-controls={`${tabsId}-panel-${index}`}
48
- id={`${tabsId}-tab-${index}`}
49
- onClick={() => setActive(index)}
50
- onKeyDown={(event) => handleKeyDown(event, index)}
51
- className={getClasses(styles.tabs__ol__item, { [styles.tabs__ol__itemActive]: active === index })}
52
- >
53
- <Text type="small" fontWeight="medium">
54
- {tab}
55
- </Text>
56
- </div>
57
- ))}
21
+ <div role="tablist" className={styles.tabs__list}>
22
+ {items.map((item: TabItem, index: number) => {
23
+ const itemClasses: string = getClasses(styles.tabs__item, {
24
+ [styles.tabs__itemActive]: !!item.isActive,
25
+ [styles.tabs__itemDisabled]: !!item.isDisabled,
26
+ })
27
+
28
+ const commonProps = {
29
+ key: index,
30
+ role: "tab" as const,
31
+ tabIndex: item.isDisabled ? -1 : 0,
32
+ "aria-selected": !!item.isActive,
33
+ "aria-disabled": !!item.isDisabled,
34
+ className: itemClasses,
35
+ }
36
+
37
+ if (item.href) {
38
+ return (
39
+ <a {...commonProps} href={item.href}>
40
+ <Text type="small" fontWeight="medium">
41
+ {item.name}
42
+ </Text>
43
+ </a>
44
+ )
45
+ }
46
+
47
+ return (
48
+ <div
49
+ {...commonProps}
50
+ onClick={item.isDisabled ? undefined : item.onClick}
51
+ onKeyDown={(e) => onKeyDown(e, item)}
52
+ >
53
+ <Text type="small" fontWeight="medium">
54
+ {item.name}
55
+ </Text>
56
+ </div>
57
+ )
58
+ })}
58
59
  </div>
59
- <section
60
- role="tabpanel"
61
- id={`${tabsId}-panel-${active}`}
62
- aria-labelledby={`${tabsId}-tab-${active}`}
63
- className="tabs__section"
64
- >
65
- {sections[active]}
66
- </section>
60
+ {children && <section className={styles.tabs__content}>{children}</section>}
67
61
  </div>
68
62
  )
69
63
  }
64
+
65
+ Tabs.displayName = "Tabs"
@@ -1,7 +1,14 @@
1
1
  import type { ReactNode } from "react"
2
2
 
3
+ export interface TabItem {
4
+ href?: string
5
+ isActive?: boolean
6
+ isDisabled?: boolean
7
+ name: string
8
+ onClick?: () => void
9
+ }
10
+
3
11
  export interface TabsProps {
4
- active?: number
5
- items: Array<string>
6
- sections: Array<ReactNode>
12
+ children?: ReactNode
13
+ items: Array<TabItem>
7
14
  }
@@ -55,3 +55,5 @@ export const Text: FC<TextProps> = (props) => {
55
55
  return <Div {...baseTextProps} />
56
56
  }
57
57
  }
58
+
59
+ Text.displayName = "Text"
@@ -5,3 +5,5 @@ import type { FC } from "react"
5
5
  export const Div: FC<DivProps> = (props) => {
6
6
  return <div {...props} className={getClasses("p", props.className)} data-ui-component="Text.Div" />
7
7
  }
8
+
9
+ Div.displayName = "Div"
@@ -5,3 +5,5 @@ import type { MicroProps } from "./Micro.types"
5
5
  export const Micro: FC<MicroProps> = (props) => {
6
6
  return <small {...props} className={getClasses("micro", props.className)} data-ui-component="Text.Micro" />
7
7
  }
8
+
9
+ Micro.displayName = "Micro"
@@ -5,3 +5,5 @@ import type { PProps } from "./P.types"
5
5
  export const P: FC<PProps> = (props) => {
6
6
  return <p {...props} className={getClasses("p", props.className)} data-ui-component="Text.P" />
7
7
  }
8
+
9
+ P.displayName = "P"
@@ -5,3 +5,5 @@ import type { SmallProps } from "./Small.types"
5
5
  export const Small: FC<SmallProps> = (props) => {
6
6
  return <small {...props} className={getClasses("small", props.className)} data-ui-component="Text.Small" />
7
7
  }
8
+
9
+ Small.displayName = "Small"
@@ -5,3 +5,5 @@ import type { TinyProps } from "./Tiny.types"
5
5
  export const Tiny: FC<TinyProps> = (props) => {
6
6
  return <small {...props} className={getClasses("tiny", props.className)} data-ui-component="Text.Tiny" />
7
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"
@@ -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"
@@ -20,7 +20,6 @@ const POSITION_CLASS_MAP = {
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 {
@@ -23,3 +23,5 @@ export const TooltipContent: FC<TooltipContentProps> = ({ children }) => {
23
23
  </div>
24
24
  )
25
25
  }
26
+
27
+ TooltipContent.displayName = "TooltipContent"
@@ -5,13 +5,15 @@ import { useTooltipContext } from "../../Tooltip.utils"
5
5
  import type { CSSProperties, FC } from "react"
6
6
 
7
7
  export const TooltipTrigger: FC<TooltipTriggerProps> = ({ children }) => {
8
- const { triggerRef, anchorName, tooltipId, isOpen } = useTooltipContext()
8
+ const { triggerRef, anchorName } = useTooltipContext()
9
9
 
10
10
  const triggerStyle: CSSProperties = { anchorName } as CSSProperties
11
11
 
12
12
  return (
13
- <div ref={triggerRef} style={triggerStyle} aria-describedby={isOpen ? tooltipId : undefined}>
13
+ <div ref={triggerRef} style={triggerStyle}>
14
14
  {children}
15
15
  </div>
16
16
  )
17
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,7 +4,7 @@ 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, FC } from "react"
7
+ import type { ChangeEventHandler, FC } from "react"
8
8
  import type { ResultListProps } from "./ResultList.types"
9
9
 
10
10
  export const ResultList: FC<ResultListProps> = ({ items, isHidden, ref }) => {
@@ -24,9 +24,9 @@ export const ResultList: FC<ResultListProps> = ({ items, isHidden, ref }) => {
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: FC<ResultListProps> = ({ items, isHidden, ref }) => {
63
63
  </ol>
64
64
  )
65
65
  }
66
+
67
+ ResultList.displayName = "ResultList"
@@ -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
  }