@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
@@ -21,26 +21,25 @@ export const Dialog: FC<DialogProps> = ({
21
21
  }) => {
22
22
  const dialogRef = useRef<HTMLDialogElement | null>(null)
23
23
  const triggerRef = useRef<Element | null>(null)
24
+ const onCloseRef = useRef<() => void>(onClose)
25
+
26
+ useEffect(() => {
27
+ onCloseRef.current = onClose
28
+ }, [onClose])
24
29
 
25
30
  useEffect(() => {
26
31
  dialogRef?.current?.scrollTo?.(0, 0)
27
32
 
28
33
  if (isOpen) {
29
34
  triggerRef.current = document.activeElement
35
+ document.documentElement.style.overflow = "hidden"
30
36
 
31
- dialogRef?.current?.showModal?.()
32
-
33
- const dialog = dialogRef.current
34
-
35
- const handleCancel = (event: Event): void => {
36
- event.preventDefault()
37
- onClose()
37
+ if (!dialogRef.current?.open) {
38
+ dialogRef?.current?.showModal?.()
38
39
  }
39
40
 
40
- dialog?.addEventListener("cancel", handleCancel)
41
-
42
41
  return (): void => {
43
- dialog?.removeEventListener("cancel", handleCancel)
42
+ document.documentElement.style.overflow = ""
44
43
 
45
44
  const trigger = triggerRef.current
46
45
  if (trigger && trigger instanceof HTMLElement) {
@@ -48,13 +47,14 @@ export const Dialog: FC<DialogProps> = ({
48
47
  }
49
48
  triggerRef.current = null
50
49
  }
51
- } else {
52
- dialogRef?.current?.close?.()
53
50
  }
54
-
55
- return undefined
56
51
  }, [isOpen])
57
52
 
53
+ const onCancel = (event: React.SyntheticEvent<HTMLDialogElement>): void => {
54
+ event.preventDefault()
55
+ onCloseRef.current()
56
+ }
57
+
58
58
  if (!isOpen) return null
59
59
 
60
60
  const onDialogClose = (event: MouseEvent<HTMLDialogElement>): boolean | void | Promise<void> => {
@@ -73,7 +73,14 @@ export const Dialog: FC<DialogProps> = ({
73
73
  })
74
74
 
75
75
  return (
76
- <dialog ref={dialogRef} className={dialogClasses} onClick={onDialogClose} role={role} data-ui-component="Dialog">
76
+ <dialog
77
+ ref={dialogRef}
78
+ className={dialogClasses}
79
+ onClick={onDialogClose}
80
+ onCancel={onCancel}
81
+ role={role}
82
+ data-ui-component="Dialog"
83
+ >
77
84
  {!!title && (
78
85
  <Flex isBetween={true} isYCentered={true} padding={8} className={dialogFlexClasses}>
79
86
  <Heading level={5} fontWeight="medium">
@@ -84,7 +91,9 @@ export const Dialog: FC<DialogProps> = ({
84
91
  </ButtonGroup>
85
92
  </Flex>
86
93
  )}
87
- {isOpen && <div className={dialogContentClasses}>{children}</div>}
94
+ <div className={dialogContentClasses}>{children}</div>
88
95
  </dialog>
89
96
  )
90
97
  }
98
+
99
+ Dialog.displayName = "Dialog"
@@ -59,3 +59,5 @@ export const Donut: FC<DonutProps> = ({ children, size, percentage = 0, color })
59
59
  </Flex>
60
60
  )
61
61
  }
62
+
63
+ Donut.displayName = "Donut"
@@ -42,3 +42,5 @@ export const Dot: FC<DotProps> = ({ colorAccent, size = 8, color = "blue" }) =>
42
42
  </div>
43
43
  )
44
44
  }
45
+
46
+ Dot.displayName = "Dot"
@@ -5,6 +5,11 @@
5
5
  user-select: none;
6
6
  }
7
7
 
8
+ .dropdownOpen [data-ui-component="Icon"] {
9
+ transition: transform 96ms ease-in-out;
10
+ transform: rotate(180deg);
11
+ }
12
+
8
13
  .dropdownDisabled {
9
14
  cursor: not-allowed;
10
15
  pointer-events: none;
@@ -1,22 +1,12 @@
1
1
  "use client"
2
2
 
3
- import {
4
- useEffect,
5
- useMemo,
6
- useState,
7
- useRef,
8
- cloneElement,
9
- Children as ReactChildren,
10
- type ReactElement,
11
- type FC,
12
- } from "react"
3
+ import { useEffect, useMemo, useState, useRef, type FC } from "react"
13
4
  import { ANIMATION_FAST } from "../../constants/animations"
14
5
  import { getClasses } from "@heliosgraphics/utils"
15
6
  import { ResultList } from "../shared/ResultList"
16
7
  import styles from "./Dropdown.module.css"
17
- import type { MouseEvent, ReactNode } from "react"
8
+ import type { KeyboardEvent, MouseEvent } from "react"
18
9
  import type { DropdownProps } from "./Dropdown.types"
19
- import type { HeliosIconType } from "../../types/icons"
20
10
 
21
11
  export const Dropdown: FC<DropdownProps> = ({ children, items, isDisabled, position = "bottom-left" }) => {
22
12
  const hoverStateRef = useRef<boolean>(false)
@@ -55,6 +45,7 @@ export const Dropdown: FC<DropdownProps> = ({ children, items, isDisabled, posit
55
45
  [styles.dropdownBottomRight]: position === "bottom-right",
56
46
  [styles.dropdownTopLeft]: position === "top-left",
57
47
  [styles.dropdownTopRight]: position === "top-right",
48
+ [styles.dropdownOpen]: isVisible,
58
49
  })
59
50
 
60
51
  const mouseEnter = (_event: MouseEvent<HTMLDivElement>): void => {
@@ -72,20 +63,15 @@ export const Dropdown: FC<DropdownProps> = ({ children, items, isDisabled, posit
72
63
  }, ANIMATION_FAST)
73
64
  }
74
65
 
75
- const renderChildren = ReactChildren.map(
76
- children as ReactElement<{ children: ReactNode; icon: HeliosIconType }>,
77
- (child: ReactElement<{ children: ReactNode; icon: HeliosIconType }>) => {
78
- const caretProps = {
79
- ...(child?.props?.icon === "caret-down" && isVisible && { icon: "caret-up" as HeliosIconType }),
80
- ...(child?.props?.icon === "chevron-down" && isVisible && { icon: "chevron-up" as HeliosIconType }),
81
- }
82
-
83
- return cloneElement(child, { ...child.props, ...caretProps })
84
- },
85
- )
86
-
87
66
  const onSetVisible = (): void => setVisible(!isVisible)
88
67
 
68
+ const onKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {
69
+ if (event.key === "Enter" || event.key === " ") {
70
+ event.preventDefault()
71
+ onSetVisible()
72
+ }
73
+ }
74
+
89
75
  const navClasses: string = getClasses(styles.dropdown__nav, isVisible && styles.dropdown__navActive)
90
76
 
91
77
  const itemsWithClose = useMemo(
@@ -106,8 +92,15 @@ export const Dropdown: FC<DropdownProps> = ({ children, items, isDisabled, posit
106
92
 
107
93
  return (
108
94
  <div className={dropdownClasses} onMouseEnter={mouseEnter} onMouseLeave={mouseLeave} data-ui-component="Dropdown">
109
- <div onClick={onSetVisible} aria-expanded={isVisible} aria-haspopup="listbox">
110
- {renderChildren}
95
+ <div
96
+ role="button"
97
+ tabIndex={0}
98
+ onClick={onSetVisible}
99
+ onKeyDown={onKeyDown}
100
+ aria-expanded={isVisible}
101
+ aria-haspopup="listbox"
102
+ >
103
+ {children}
111
104
  </div>
112
105
  <nav
113
106
  className={navClasses}
@@ -120,3 +113,5 @@ export const Dropdown: FC<DropdownProps> = ({ children, items, isDisabled, posit
120
113
  </div>
121
114
  )
122
115
  }
116
+
117
+ Dropdown.displayName = "Dropdown"
@@ -10,3 +10,5 @@ export const Fieldset: FC<FieldsetProps> = ({ children, legend }) => {
10
10
  </fieldset>
11
11
  )
12
12
  }
13
+
14
+ Fieldset.displayName = "Fieldset"
@@ -25,6 +25,7 @@ export const meta: HeliosAttributeMeta<FlexBaseProps> = {
25
25
  isBetween: { type: "boolean", isOptional: true },
26
26
  isCentered: { type: "boolean", isOptional: true },
27
27
  isColumn: { type: "boolean", isOptional: true },
28
+ isFullHeight: { type: "boolean", isOptional: true },
28
29
  isFullWidth: { type: "boolean", isOptional: true },
29
30
  isInline: { type: "boolean", isOptional: true },
30
31
  isNoWrap: { type: "boolean", isOptional: true },
@@ -1,15 +1,35 @@
1
1
  import { getFlexUtility, getSafeFlexProps } from "../Flex/Flex.utils"
2
2
  import type { FlexProps } from "./Flex.types"
3
- import type { FC } from "react"
3
+ import type { FC, KeyboardEvent } from "react"
4
4
 
5
5
  export const Flex: FC<FlexProps> = (props) => {
6
6
  const { ref, ...restProps } = props
7
7
  const flexClasses: string = getFlexUtility(restProps)
8
8
  const safeProps = getSafeFlexProps(restProps)
9
9
 
10
+ const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
11
+ if (restProps.onClick && (event.key === "Enter" || event.key === " ")) {
12
+ event.preventDefault()
13
+ restProps.onClick(event as unknown as React.MouseEvent<HTMLDivElement>)
14
+ }
15
+ }
16
+
17
+ const a11yProps = restProps.onClick
18
+ ? { role: (restProps.role ?? "button") as string, tabIndex: restProps.tabIndex ?? 0, onKeyDown: handleKeyDown }
19
+ : {}
20
+
10
21
  return (
11
- <div {...safeProps} className={flexClasses} onClick={restProps.onClick} ref={ref}>
22
+ <div
23
+ {...safeProps}
24
+ className={flexClasses}
25
+ onClick={restProps.onClick}
26
+ ref={ref}
27
+ data-ui-component="Flex"
28
+ {...a11yProps}
29
+ >
12
30
  {restProps.children}
13
31
  </div>
14
32
  )
15
33
  }
34
+
35
+ Flex.displayName = "Flex"
@@ -14,6 +14,7 @@ export interface FlexBaseProps {
14
14
  isBetween?: boolean
15
15
  isCentered?: boolean
16
16
  isColumn?: boolean
17
+ isFullHeight?: boolean
17
18
  isFullWidth?: boolean
18
19
  isInline?: boolean
19
20
  isNoWrap?: boolean
@@ -40,6 +40,9 @@ describe("getFlexUtility", () => {
40
40
  it("Generates without duplicated classes", () =>
41
41
  expect(getFlexUtility(MOCK_FLEX_DUPLICATE)).toEqual(MOCK_FLEX_DUPLICATE_CLASSES))
42
42
 
43
+ it("Generates xAlign center class", () =>
44
+ expect(getFlexUtility({ children: null, xAlign: "center" })).toEqual("flex flex-x-center"))
45
+
43
46
  const MOCK_FLEX_RESPONSIVE_CLASSES = `flex mobile:p-0 tablet:p-8 p-16`
44
47
  const MOCK_FLEX_RESPONSIVE: FlexProps = {
45
48
  children: null,
@@ -60,7 +63,7 @@ describe("getFlexUtility", () => {
60
63
  })
61
64
 
62
65
  describe("getSafeFlexProps", () => {
63
- const MOCK_FLEX_ATTRIBUTES: unknown = {
66
+ const MOCK_FLEX_ATTRIBUTES: Record<string, unknown> = {
64
67
  children: null,
65
68
  className: "xo",
66
69
  draggable: true,
@@ -21,10 +21,12 @@ export const getFlexUtility = (props?: FlexProps): string => {
21
21
  if (props.yAlign === "start") flexClasses.add("align-flex-start")
22
22
  if (props.yAlign === "end") flexClasses.add("align-flex-end")
23
23
  if (props.xAlign === "start") flexClasses.add("justify-flex-start")
24
+ if (props.xAlign === "center") flexClasses.add("flex-x-center")
24
25
  if (props.xAlign === "end") flexClasses.add("justify-flex-end")
25
26
  if (props.isBetween) flexClasses.add("space-between")
26
27
  if (props.isAround) flexClasses.add("space-around")
27
28
  if (props.onClick) flexClasses.add("cursor-pointer")
29
+ if (props.isFullHeight) flexClasses.add("hp-100")
28
30
  if (props.isFullWidth) flexClasses.add("wp-100")
29
31
  if (props.isNoWrap) flexClasses.add("nowrap")
30
32
  if (props.elevation === "small") flexClasses.add("elevation-sm")
@@ -128,7 +130,7 @@ export const getRadius = (radiusValue?: ResponsiveRadiusType): string => {
128
130
  }
129
131
 
130
132
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
- export const getSafeFlexProps = (props: any): Partial<FlexProps> => {
133
+ export const getSafeFlexProps = (props: Record<string, any>): Record<string, unknown> => {
132
134
  const {
133
135
  gap: _gap,
134
136
  isAround: _isAround,
@@ -136,6 +138,7 @@ export const getSafeFlexProps = (props: any): Partial<FlexProps> => {
136
138
  isCentered: _isCentered,
137
139
  isColumn: _isColumn,
138
140
  isColumnCentered: _isColumnCentered,
141
+ isFullHeight: _isFullHeight,
139
142
  isFullWidth: _isFullWidth,
140
143
  isInline: _isInline,
141
144
  isStretch: _isStretch,
@@ -13,3 +13,5 @@ export const Grid: FC<GridProps> = ({ columns, children }) => {
13
13
  </div>
14
14
  )
15
15
  }
16
+
17
+ Grid.displayName = "Grid"
@@ -15,4 +15,9 @@ export const meta: HeliosAttributeMeta<HeadingBaseProps> = {
15
15
  level: {
16
16
  type: "0 | 1 | 2 | 3 | 4 | 5 | 6",
17
17
  },
18
+ lineHeight: {
19
+ type: "number",
20
+ isOptional: true,
21
+ description: "Overrides the default line-height",
22
+ },
18
23
  }
@@ -12,7 +12,7 @@ import type { HeadingProps } from "./Heading.types"
12
12
  import type { FC, CSSProperties } from "react"
13
13
 
14
14
  export const Heading: FC<HeadingProps> = (props) => {
15
- const { level, lineClamp, style, className, ...rest } = props
15
+ const { level, lineClamp, lineHeight, style, className, ...rest } = props
16
16
 
17
17
  const headingClasses: string = getClasses(className, styles.heading, {
18
18
  [styles.headingPrimary]: props.emphasis === "primary",
@@ -32,8 +32,12 @@ export const Heading: FC<HeadingProps> = (props) => {
32
32
  }
33
33
  : undefined
34
34
 
35
+ const lineHeightStyle: CSSProperties | undefined = lineHeight !== undefined ? { lineHeight } : undefined
36
+
35
37
  const mergedStyle: CSSProperties | undefined =
36
- style || lineClampStyle ? { ...(style || {}), ...(lineClampStyle || {}) } : undefined
38
+ style || lineClampStyle || lineHeightStyle
39
+ ? { ...(style || {}), ...(lineClampStyle || {}), ...(lineHeightStyle || {}) }
40
+ : undefined
37
41
 
38
42
  const allProps: Omit<HeadingProps, "level"> = {
39
43
  ...rest,
@@ -44,20 +48,22 @@ export const Heading: FC<HeadingProps> = (props) => {
44
48
 
45
49
  switch (level) {
46
50
  case 0:
47
- return <H0 {...allProps} />
51
+ return <H0 {...allProps} data-ui-component="Heading" />
48
52
  case 1:
49
- return <H1 {...allProps} />
53
+ return <H1 {...allProps} data-ui-component="Heading" />
50
54
  case 2:
51
- return <H2 {...allProps} />
55
+ return <H2 {...allProps} data-ui-component="Heading" />
52
56
  case 3:
53
- return <H3 {...allProps} />
57
+ return <H3 {...allProps} data-ui-component="Heading" />
54
58
  case 4:
55
- return <H4 {...allProps} />
59
+ return <H4 {...allProps} data-ui-component="Heading" />
56
60
  case 5:
57
- return <H5 {...allProps} />
61
+ return <H5 {...allProps} data-ui-component="Heading" />
58
62
  case 6:
59
- return <H6 {...allProps} />
63
+ return <H6 {...allProps} data-ui-component="Heading" />
60
64
  default:
61
65
  return null
62
66
  }
63
67
  }
68
+
69
+ Heading.displayName = "Heading"
@@ -3,6 +3,7 @@ import type { TextBaseProps } from "../Text/Text.types"
3
3
 
4
4
  export interface HeadingBaseProps {
5
5
  level: 0 | 1 | 2 | 3 | 4 | 5 | 6
6
+ lineHeight?: number
6
7
  }
7
8
 
8
9
  export type HeadingProps = HeadingBaseProps & Omit<TextBaseProps, "type"> & HTMLAttributes<HTMLHeadingElement>
@@ -8,3 +8,5 @@ export const H0: FC<H0Props> = (props) => {
8
8
 
9
9
  return <h1 {...props} className={h0Classes} data-ui-component="Heading.H0" />
10
10
  }
11
+
12
+ H0.displayName = "H0"
@@ -5,3 +5,5 @@ import type { H1Props } from "./H1.types"
5
5
  export const H1: FC<H1Props> = (props) => {
6
6
  return <h1 {...props} className={getClasses("h1", props.className)} data-ui-component="Heading.H1" />
7
7
  }
8
+
9
+ H1.displayName = "H1"
@@ -5,3 +5,5 @@ import type { H2Props } from "./H2.types"
5
5
  export const H2: FC<H2Props> = (props) => {
6
6
  return <h2 {...props} className={getClasses("h2", props.className)} data-ui-component="Heading.H2" />
7
7
  }
8
+
9
+ H2.displayName = "H2"
@@ -5,3 +5,5 @@ import type { H3Props } from "./H3.types"
5
5
  export const H3: FC<H3Props> = (props) => {
6
6
  return <h3 {...props} className={getClasses("h3", props.className)} data-ui-component="Heading.H3" />
7
7
  }
8
+
9
+ H3.displayName = "H3"
@@ -5,3 +5,5 @@ import type { H4Props } from "./H4.types"
5
5
  export const H4: FC<H4Props> = (props) => {
6
6
  return <h4 {...props} className={getClasses("h4", props.className)} data-ui-component="Heading.H4" />
7
7
  }
8
+
9
+ H4.displayName = "H4"
@@ -5,3 +5,5 @@ import type { H5Props } from "./H5.types"
5
5
  export const H5: FC<H5Props> = (props) => {
6
6
  return <h5 {...props} className={getClasses("h5", props.className)} data-ui-component="Heading.H5" />
7
7
  }
8
+
9
+ H5.displayName = "H5"
@@ -5,3 +5,5 @@ import type { H6Props } from "./H6.types"
5
5
  export const H6: FC<H6Props> = (props) => {
6
6
  return <h6 {...props} className={getClasses("h6", props.className)} data-ui-component="Heading.H6" />
7
7
  }
8
+
9
+ H6.displayName = "H6"
@@ -28,3 +28,5 @@ export const Icon: FC<IconProps> = ({ icon, className, emphasis, size }) => {
28
28
  />
29
29
  )
30
30
  }
31
+
32
+ Icon.displayName = "Icon"
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import { useId, type FC } from "react"
3
+ import { useId, forwardRef } from "react"
4
4
  import { getClasses } from "@heliosgraphics/utils"
5
5
  import { Button } from "../Button"
6
6
  import { ButtonGroup } from "../ButtonGroup"
@@ -13,104 +13,112 @@ import styles from "./Input.module.css"
13
13
  import type { InputProps } from "./Input.types"
14
14
  import type { ResultItem } from "../shared/ResultList"
15
15
 
16
- export const Input: FC<InputProps> = ({
17
- className,
18
- helperText,
19
- id,
20
- isDisabled,
21
- autoComplete,
22
- autoFocus,
23
- isLabelHidden,
24
- isLoading,
25
- isRequired,
26
- label,
27
- maxLength,
28
- name,
29
- onBlur,
30
- onChange,
31
- onClear,
32
- onFocus,
33
- onKeyDown,
34
- onKeyUp,
35
- placeholder,
36
- results,
37
- showResults,
38
- type = "text",
39
- value,
40
- }) => {
41
- const generatedId: string = useId()
42
- const htmlFor: string = id || generatedId
43
- const helperId: string = `${htmlFor}-helper`
16
+ export const Input = forwardRef<HTMLInputElement, InputProps>(
17
+ (
18
+ {
19
+ className,
20
+ helperText,
21
+ id,
22
+ isDisabled,
23
+ autoComplete,
24
+ autoFocus,
25
+ isLabelHidden,
26
+ isLoading,
27
+ isRequired,
28
+ label,
29
+ maxLength,
30
+ name,
31
+ onBlur,
32
+ onChange,
33
+ onClear,
34
+ onFocus,
35
+ onKeyDown,
36
+ onKeyUp,
37
+ placeholder,
38
+ results,
39
+ showResults,
40
+ type = "text",
41
+ value,
42
+ },
43
+ ref,
44
+ ) => {
45
+ const generatedId: string = useId()
46
+ const htmlFor: string = id || generatedId
47
+ const helperId: string = `${htmlFor}-helper`
44
48
 
45
- const filteredItems: Array<ResultItem> =
46
- results
47
- ?.filter((item) => {
48
- const lowerCaseItem: string = item.name.toLowerCase()
49
- const lowerCaseFilter: string | undefined = value?.toLowerCase()
49
+ const filteredItems: Array<ResultItem> =
50
+ results
51
+ ?.filter((item) => {
52
+ const lowerCaseItem: string = item.name.toLowerCase()
53
+ const lowerCaseFilter: string | undefined = value?.toLowerCase()
50
54
 
51
- return lowerCaseFilter && lowerCaseItem.includes(lowerCaseFilter)
52
- })
53
- .filter(Boolean) || []
55
+ return lowerCaseFilter && lowerCaseItem.includes(lowerCaseFilter)
56
+ })
57
+ .filter(Boolean) || []
54
58
 
55
- const showingResults: boolean = Boolean(!!filteredItems?.length && showResults)
59
+ const showingResults: boolean = Boolean(!!filteredItems?.length && showResults)
56
60
 
57
- const inputClasses: string = getClasses(styles.input, "relative flex flex-column", className, {
58
- [styles.inputDisabled]: isDisabled,
59
- [styles.inputShowingResults]: showingResults,
60
- })
61
+ const inputClasses: string = getClasses(styles.input, "relative flex flex-column", className, {
62
+ [styles.inputDisabled]: isDisabled,
63
+ [styles.inputShowingResults]: showingResults,
64
+ })
61
65
 
62
- return (
63
- <div className={inputClasses} data-ui-component="Input">
64
- <InputLabel id={htmlFor} label={label} isDisabled={!!isDisabled} isHidden={!!isLabelHidden} />
65
- <Flex className="grow-1">
66
- {onClear && !!value && (
67
- <ButtonGroup className={styles.input__clear}>
68
- <Button
69
- value="Clear"
70
- size="tiny"
71
- intent="neutral"
72
- icon="x"
73
- onClick={onClear}
74
- isIconOnly={true}
75
- isRounded={true}
76
- />
77
- </ButtonGroup>
78
- )}
79
- <input
80
- className={styles.input__input}
81
- disabled={isDisabled}
82
- autoComplete={autoComplete}
83
- autoFocus={autoFocus}
84
- id={htmlFor}
85
- maxLength={maxLength}
86
- name={name}
87
- onBlur={onBlur}
88
- type={type}
89
- onChange={onChange}
90
- onFocus={onFocus}
91
- onKeyDown={onKeyDown}
92
- onKeyUp={onKeyUp}
93
- placeholder={placeholder}
94
- required={isRequired}
95
- value={value}
96
- aria-describedby={helperText ? helperId : undefined}
97
- />
98
- {isLoading && (
99
- <div className={styles.input__loading}>
100
- <Loading size={20} />
66
+ return (
67
+ <div className={inputClasses} data-ui-component="Input">
68
+ <InputLabel id={htmlFor} label={label} isDisabled={!!isDisabled} isHidden={!!isLabelHidden} />
69
+ <Flex className="grow-1">
70
+ {onClear && !!value && (
71
+ <ButtonGroup className={styles.input__clear}>
72
+ <Button
73
+ value="Clear"
74
+ size="tiny"
75
+ intent="neutral"
76
+ icon="x"
77
+ onClick={onClear}
78
+ isIconOnly={true}
79
+ isRounded={true}
80
+ />
81
+ </ButtonGroup>
82
+ )}
83
+ <input
84
+ ref={ref}
85
+ className={styles.input__input}
86
+ disabled={isDisabled}
87
+ autoComplete={autoComplete}
88
+ autoFocus={autoFocus}
89
+ id={htmlFor}
90
+ maxLength={maxLength}
91
+ name={name}
92
+ onBlur={onBlur}
93
+ type={type}
94
+ onChange={onChange}
95
+ onFocus={onFocus}
96
+ onKeyDown={onKeyDown}
97
+ onKeyUp={onKeyUp}
98
+ placeholder={placeholder}
99
+ required={isRequired}
100
+ value={value}
101
+ aria-describedby={helperText ? helperId : undefined}
102
+ />
103
+ {isLoading && (
104
+ <div className={styles.input__loading}>
105
+ <Loading size={20} />
106
+ </div>
107
+ )}
108
+ </Flex>
109
+ {showingResults && (
110
+ <div className={styles.input__results}>
111
+ <ResultList items={filteredItems} />
101
112
  </div>
102
113
  )}
103
- </Flex>
104
- {showingResults && (
105
- <div className={styles.input__results}>
106
- <ResultList items={filteredItems} />
107
- </div>
108
- )}
109
- {!!helperText && (
110
- <Text type="tiny" emphasis="tertiary" className={styles.input__helper} id={helperId}>
111
- {helperText}
112
- </Text>
113
- )}
114
- </div>
115
- )
116
- }
114
+ {!!helperText && (
115
+ <Text type="tiny" emphasis="tertiary" className={styles.input__helper} id={helperId}>
116
+ {helperText}
117
+ </Text>
118
+ )}
119
+ </div>
120
+ )
121
+ },
122
+ )
123
+
124
+ Input.displayName = "Input"
@@ -29,3 +29,5 @@ export const Layout: FC<LayoutProps> = (props) => {
29
29
  </LayoutProvider>
30
30
  )
31
31
  }
32
+
33
+ Layout.displayName = "Layout"