@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
@@ -23,3 +23,5 @@ export const LayoutAside: FC<LayoutAsideProps> = ({ children }) => {
23
23
  </aside>
24
24
  )
25
25
  }
26
+
27
+ LayoutAside.displayName = "LayoutAside"
@@ -15,3 +15,5 @@ export const LayoutAsideContent: FC<LayoutAsideContentProps> = (props) => {
15
15
  </section>
16
16
  )
17
17
  }
18
+
19
+ LayoutAsideContent.displayName = "LayoutAsideContent"
@@ -15,3 +15,5 @@ export const LayoutAsideFooter: FC<LayoutAsideFooterProps> = (props) => {
15
15
  </footer>
16
16
  )
17
17
  }
18
+
19
+ LayoutAsideFooter.displayName = "LayoutAsideFooter"
@@ -25,3 +25,5 @@ export const LayoutAsideToggle: FC<LayoutAsideToggleProps> = () => {
25
25
  </ButtonGroup>
26
26
  )
27
27
  }
28
+
29
+ LayoutAsideToggle.displayName = "LayoutAsideToggle"
@@ -19,3 +19,5 @@ export const LayoutMain: FC<LayoutMainProps> = (props) => {
19
19
  </main>
20
20
  )
21
21
  }
22
+
23
+ LayoutMain.displayName = "LayoutMain"
@@ -39,3 +39,5 @@ export const LayoutMainContent: FC<LayoutMainContentProps> = (props) => {
39
39
  </section>
40
40
  )
41
41
  }
42
+
43
+ LayoutMainContent.displayName = "LayoutMainContent"
@@ -20,3 +20,5 @@ export const LayoutNavigation: FC<LayoutNavigationProps> = (props) => {
20
20
  </nav>
21
21
  )
22
22
  }
23
+
24
+ LayoutNavigation.displayName = "LayoutNavigation"
@@ -20,3 +20,5 @@ export const LayoutSubNavigation: FC<LayoutSubNavigationProps> = (props) => {
20
20
  </nav>
21
21
  )
22
22
  }
23
+
24
+ LayoutSubNavigation.displayName = "LayoutSubNavigation"
@@ -53,3 +53,5 @@ export const Loading: FC<LoadingProps> = ({ className, size, emphasis }) => {
53
53
  </svg>
54
54
  )
55
55
  }
56
+
57
+ Loading.displayName = "Loading"
@@ -23,3 +23,5 @@ export const Markdown: FC<MarkdownProps> = ({ text, children, isNonSelectable })
23
23
  </div>
24
24
  )
25
25
  }
26
+
27
+ Markdown.displayName = "Markdown"
@@ -31,7 +31,11 @@ export const Masonry: FC<MasonryProps> = ({
31
31
  return (
32
32
  <>
33
33
  <style dangerouslySetInnerHTML={{ __html: mediaStyles }} />
34
- <div className={id}>{children}</div>
34
+ <div className={id} data-ui-component="Masonry">
35
+ {children}
36
+ </div>
35
37
  </>
36
38
  )
37
39
  }
40
+
41
+ Masonry.displayName = "Masonry"
@@ -14,3 +14,5 @@ export const Menu: FC<MenuProps> = (props) => {
14
14
  </nav>
15
15
  )
16
16
  }
17
+
18
+ Menu.displayName = "Menu"
@@ -84,3 +84,5 @@ export const MenuCategory: FC<MenuCategoryProps> = ({
84
84
  </Flex>
85
85
  )
86
86
  }
87
+
88
+ MenuCategory.displayName = "MenuCategory"
@@ -21,3 +21,5 @@ export const MenuFilter: FC<MenuFilterProps> = ({ value, onClear, onChange }) =>
21
21
  </div>
22
22
  )
23
23
  }
24
+
25
+ MenuFilter.displayName = "MenuFilter"
@@ -69,3 +69,5 @@ export const MenuItem: FC<MenuItemProps> = ({
69
69
  </Flex>
70
70
  )
71
71
  }
72
+
73
+ MenuItem.displayName = "MenuItem"
@@ -1,5 +1,5 @@
1
1
  import styles from "./Overlay.module.css"
2
- import type { FC, MouseEvent } from "react"
2
+ import type { FC, MouseEvent, KeyboardEvent } from "react"
3
3
  import type { OverlayProps } from "./Overlay.types"
4
4
  import { getClasses } from "@heliosgraphics/utils"
5
5
 
@@ -11,6 +11,13 @@ export const Overlay: FC<OverlayProps> = ({ onClose, isCentered, children, isOpe
11
11
  return onClose?.()
12
12
  }
13
13
 
14
+ const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {
15
+ if (event.key === "Enter" || event.key === " ") {
16
+ event.preventDefault()
17
+ onClose?.()
18
+ }
19
+ }
20
+
14
21
  const contentClasses: string = getClasses(styles.overlay__content, {
15
22
  [styles.overlay__contentCentered]: isCentered,
16
23
  })
@@ -25,7 +32,16 @@ export const Overlay: FC<OverlayProps> = ({ onClose, isCentered, children, isOpe
25
32
  data-open={isOpen}
26
33
  >
27
34
  <div className={contentClasses}>{children}</div>
28
- <div className={styles.overlay__layer} onClick={hideFunction} />
35
+ <div
36
+ className={styles.overlay__layer}
37
+ onClick={hideFunction}
38
+ onKeyDown={handleKeyDown}
39
+ role="button"
40
+ tabIndex={0}
41
+ aria-label="Close overlay"
42
+ />
29
43
  </section>
30
44
  )
31
45
  }
46
+
47
+ Overlay.displayName = "Overlay"
@@ -43,3 +43,5 @@ export const Pie: FC<PieProps> = ({ color, size, data }) => {
43
43
  </svg>
44
44
  )
45
45
  }
46
+
47
+ Pie.displayName = "Pie"
@@ -28,8 +28,16 @@ export const meta: HeliosAttributeMeta<PillProps> = {
28
28
  type: "HeliosIconType",
29
29
  isOptional: true,
30
30
  },
31
+ isEllipsis: {
32
+ type: "boolean",
33
+ isOptional: true,
34
+ },
31
35
  isLabelHidden: {
32
- type: "(event?: unknown) => void",
36
+ type: "boolean",
37
+ isOptional: true,
38
+ },
39
+ isLoading: {
40
+ type: "boolean",
33
41
  isOptional: true,
34
42
  },
35
43
  isMono: {
@@ -51,3 +51,14 @@
51
51
  .pillTiny.pillRounded:not(.pillIconOnly) {
52
52
  padding: 0 8px;
53
53
  }
54
+
55
+ .pillEllipsis {
56
+ overflow: hidden;
57
+ max-width: 100%;
58
+ }
59
+
60
+ .pillEllipsis :global(.text) {
61
+ overflow: hidden;
62
+ text-overflow: ellipsis;
63
+ white-space: nowrap;
64
+ }
@@ -3,8 +3,9 @@ import { getColorClasses } from "../../utils/colors"
3
3
  import { Flex } from "../Flex"
4
4
  import { Text } from "../Text"
5
5
  import { Icon } from "../Icon"
6
+ import { Loading } from "../Loading"
6
7
  import styles from "./Pill.module.css"
7
- import { memo, type FC } from "react"
8
+ import { memo, type FC, type KeyboardEvent } from "react"
8
9
  import type { PillProps } from "./Pill.types"
9
10
 
10
11
  const PILL_ICON_SIZE = {
@@ -13,13 +14,21 @@ const PILL_ICON_SIZE = {
13
14
  normal: 24,
14
15
  } as const satisfies Record<string, number>
15
16
 
17
+ const PILL_LOADING_SIZE = {
18
+ tiny: 10,
19
+ small: 10,
20
+ normal: 20,
21
+ } as const satisfies Record<string, 10 | 20 | 40>
22
+
16
23
  export const Pill: FC<PillProps> = memo(
17
24
  ({
18
25
  appearance = "light",
19
26
  color = "gray",
20
27
  className,
21
28
  icon,
29
+ isEllipsis,
22
30
  isLabelHidden,
31
+ isLoading,
23
32
  isMono,
24
33
  isRounded,
25
34
  label,
@@ -38,21 +47,35 @@ export const Pill: FC<PillProps> = memo(
38
47
  [styles.pillTiny]: size === "tiny",
39
48
 
40
49
  [styles.pillIconOnly]: !!icon && isLabelHidden,
50
+ [styles.pillEllipsis]: isEllipsis,
41
51
  })
42
52
 
43
53
  const isSmall: boolean = size !== "normal"
44
54
  const pillTextSize = isSmall ? "tiny" : "small"
45
55
 
56
+ const onKeyDown = onClick
57
+ ? (event: KeyboardEvent<HTMLDivElement>): void => {
58
+ if (event.key === "Enter" || event.key === " ") {
59
+ event.preventDefault()
60
+ onClick(event as unknown as React.MouseEvent<HTMLDivElement>)
61
+ }
62
+ }
63
+ : undefined
64
+
65
+ const isInteractive: boolean = !!onClick && !isLoading
66
+
46
67
  return (
47
68
  <Flex
48
- {...(onClick && { onClick })}
69
+ {...(isInteractive && { onClick, role: "button", tabIndex: 0 })}
70
+ {...(isInteractive && onKeyDown && { onKeyDown })}
49
71
  className={pillClasses}
50
72
  isCentered={true}
51
73
  gap={2}
52
74
  aria-label={isLabelHidden ? label : undefined}
53
75
  data-ui-component="Pill"
54
76
  >
55
- {icon && <Icon size={PILL_ICON_SIZE[size] || 16} icon={icon} />}
77
+ {isLoading && <Loading size={PILL_LOADING_SIZE[size] || 20} />}
78
+ {!isLoading && icon && <Icon size={PILL_ICON_SIZE[size] || 16} icon={icon} />}
56
79
  {!isLabelHidden && (
57
80
  <Text type={pillTextSize} whiteSpace="nowrap" {...(isMono && { fontFamily: "mono" })} fontWeight="medium">
58
81
  {label}
@@ -62,3 +85,5 @@ export const Pill: FC<PillProps> = memo(
62
85
  )
63
86
  },
64
87
  )
88
+
89
+ Pill.displayName = "Pill"
@@ -8,7 +8,9 @@ export interface PillProps {
8
8
  color: HeliosColorType
9
9
  className?: string
10
10
  icon?: HeliosIconType
11
+ isEllipsis?: boolean
11
12
  isLabelHidden?: boolean
13
+ isLoading?: boolean
12
14
  isMono?: boolean
13
15
  isRounded?: boolean
14
16
  label: string
@@ -20,3 +20,5 @@ export const Placeholder: FC<PlaceholderProps> = ({ children, height }) => {
20
20
  </Flex>
21
21
  )
22
22
  }
23
+
24
+ Placeholder.displayName = "Placeholder"
@@ -26,3 +26,5 @@ export const Progress: FC<ProgressProps> = ({
26
26
  />
27
27
  )
28
28
  }
29
+
30
+ Progress.displayName = "Progress"
@@ -69,3 +69,5 @@ export const Radio: FC<RadioProps> = ({
69
69
  </div>
70
70
  )
71
71
  }
72
+
73
+ Radio.displayName = "Radio"
@@ -45,3 +45,5 @@ export const Range: FC<RangeProps> = ({
45
45
  </div>
46
46
  )
47
47
  }
48
+
49
+ Range.displayName = "Range"
@@ -0,0 +1,19 @@
1
+ "use client"
2
+
3
+ import { createContext, useContext } from "react"
4
+
5
+ export interface SegmentsContextValue {
6
+ activeValue: string
7
+ onSelect: (value: string) => void
8
+ registerRef: (value: string, el: HTMLButtonElement | null) => void
9
+ }
10
+
11
+ export const SegmentsContext = createContext<SegmentsContextValue | null>(null)
12
+
13
+ export const useSegments = (): SegmentsContextValue => {
14
+ const context = useContext(SegmentsContext)
15
+
16
+ if (!context) throw new Error("SegmentButton must be used within Segments")
17
+
18
+ return context
19
+ }
@@ -12,6 +12,10 @@ export const meta: HeliosAttributeMeta<SegmentsBaseProps> = {
12
12
  ],
13
13
  _status: "nominal",
14
14
  _category: "core",
15
+ defaultValue: {
16
+ type: "string",
17
+ isOptional: true,
18
+ },
15
19
  isFullWidth: {
16
20
  type: "boolean",
17
21
  isOptional: true,
@@ -1,41 +1,22 @@
1
1
  "use client"
2
2
 
3
- import {
4
- type FC,
5
- useState,
6
- useRef,
7
- useEffect,
8
- useCallback,
9
- Children,
10
- isValidElement,
11
- cloneElement,
12
- type ReactElement,
13
- } from "react"
3
+ import { type FC, useState, useRef, useEffect, useCallback } from "react"
14
4
  import { Flex } from "../Flex"
15
5
  import styles from "./Segments.module.css"
16
6
  import { getClasses } from "@heliosgraphics/utils"
17
- import { SegmentButton } from "./components/SegmentButton"
7
+ import { SegmentsContext } from "./Segments.context"
18
8
  import type { SegmentsProps } from "./Segments.types"
19
- import type { SegmentButtonProps } from "./components/SegmentButton/SegmentButton.types"
20
9
 
21
- export const Segments: FC<SegmentsProps> = ({ children, isFullWidth, isSmall }) => {
22
- const validChildren: ReactElement<SegmentButtonProps>[] = Children.toArray(children).filter(
23
- (child): child is ReactElement<SegmentButtonProps> => isValidElement(child) && child.type === SegmentButton,
24
- )
25
-
26
- const firstValue: string = validChildren.length > 0 ? validChildren[0]?.props?.value || "" : ""
27
- const defaultValue: string = ""
28
-
29
- const [activeValue, setActiveValue] = useState<string>(defaultValue || firstValue)
30
- const buttonRefs = useRef<(HTMLButtonElement | null)[]>([])
10
+ export const Segments: FC<SegmentsProps> = ({ children, isFullWidth, isSmall, defaultValue }) => {
11
+ const [activeValue, setActiveValue] = useState<string>(defaultValue ?? "")
12
+ const buttonRefs = useRef<Map<string, HTMLButtonElement>>(new Map())
31
13
  const containerRef = useRef<HTMLDivElement>(null)
32
14
  const sliderRef = useRef<HTMLDivElement>(null)
33
15
 
34
16
  const updateSliderPosition = useCallback((): void => {
35
17
  if (!containerRef.current || !sliderRef.current || !activeValue) return
36
18
 
37
- const activeIndex: number = validChildren.findIndex((child) => child.props.value === activeValue)
38
- const activeButton: HTMLButtonElement | null | undefined = buttonRefs.current[activeIndex]
19
+ const activeButton: HTMLButtonElement | undefined = buttonRefs.current.get(activeValue)
39
20
 
40
21
  if (!activeButton) return
41
22
 
@@ -48,7 +29,7 @@ export const Segments: FC<SegmentsProps> = ({ children, isFullWidth, isSmall })
48
29
  sliderRef.current.style.transform = `translateX(${left}px)`
49
30
  sliderRef.current.style.width = `${width}px`
50
31
  sliderRef.current.style.opacity = "1"
51
- }, [activeValue, validChildren])
32
+ }, [activeValue])
52
33
 
53
34
  useEffect(() => {
54
35
  const timer: ReturnType<typeof setTimeout> = setTimeout(updateSliderPosition, 0)
@@ -61,27 +42,38 @@ export const Segments: FC<SegmentsProps> = ({ children, isFullWidth, isSmall })
61
42
  }
62
43
  }, [updateSliderPosition])
63
44
 
45
+ const onSelect = useCallback((value: string): void => {
46
+ setActiveValue(value)
47
+ }, [])
48
+
49
+ const registerRef = useCallback((value: string, el: HTMLButtonElement | null): void => {
50
+ if (el) {
51
+ buttonRefs.current.set(value, el)
52
+ } else {
53
+ buttonRefs.current.delete(value)
54
+ }
55
+ }, [])
56
+
64
57
  const segmentedControlClasses: string = getClasses(styles.segmentedControl, {
65
58
  [styles.segmentedControlSmall]: isSmall,
66
59
  [styles.segmentedControlFull]: isFullWidth,
67
60
  })
68
61
 
69
62
  return (
70
- <Flex className={segmentedControlClasses} isYCentered={true} gap={2} isInline={!isFullWidth} ref={containerRef}>
71
- {validChildren.map((child: ReactElement<SegmentButtonProps>, index: number) =>
72
- cloneElement<SegmentButtonProps>(child, {
73
- key: child.props.value,
74
- isActive: child.props.value === activeValue,
75
- onClick: () => {
76
- setActiveValue(child.props.value)
77
- child.props.onClick?.()
78
- },
79
- ref: (el: HTMLButtonElement | null) => {
80
- buttonRefs.current[index] = el
81
- },
82
- }),
83
- )}
84
- <div ref={sliderRef} className={styles.segments__slider} />
85
- </Flex>
63
+ <SegmentsContext value={{ activeValue, onSelect, registerRef }}>
64
+ <Flex
65
+ className={segmentedControlClasses}
66
+ isYCentered={true}
67
+ gap={2}
68
+ isInline={!isFullWidth}
69
+ ref={containerRef}
70
+ data-ui-component="Segments"
71
+ >
72
+ {children}
73
+ <div ref={sliderRef} className={styles.segments__slider} />
74
+ </Flex>
75
+ </SegmentsContext>
86
76
  )
87
77
  }
78
+
79
+ Segments.displayName = "Segments"
@@ -2,6 +2,7 @@ import type { SegmentButtonProps } from "./components/SegmentButton/SegmentButto
2
2
  import type { FC, PropsWithChildren, ReactElement } from "react"
3
3
 
4
4
  export interface SegmentsBaseProps {
5
+ defaultValue?: string
5
6
  isFullWidth?: boolean
6
7
  isSmall?: boolean
7
8
  }
@@ -26,10 +26,6 @@ export const meta: HeliosAttributeMeta<SegmentButtonProps> = {
26
26
  type: "() => void",
27
27
  isOptional: true,
28
28
  },
29
- ref: {
30
- type: "Ref<HTMLButtonElement>",
31
- isOptional: true,
32
- },
33
29
  value: {
34
30
  type: "string",
35
31
  },
@@ -1,22 +1,47 @@
1
1
  import styles from "./SegmentButton.module.css"
2
2
  import { getClasses } from "@heliosgraphics/utils"
3
3
  import { Icon } from "../../../Icon"
4
- import type { FC } from "react"
4
+ import { useSegments } from "../../Segments.context"
5
+ import { useCallback, type FC } from "react"
5
6
  import type { HeliosIconType } from "../../../../types/icons"
6
7
  import type { SegmentButtonProps } from "./SegmentButton.types"
7
8
 
8
- export const SegmentButton: FC<SegmentButtonProps> = ({ isActive, value, icon, iconLeft, iconRight, onClick, ref }) => {
9
+ export const SegmentButton: FC<SegmentButtonProps> = ({
10
+ value,
11
+ icon,
12
+ iconLeft,
13
+ iconRight,
14
+ isActive: isActiveProp,
15
+ onClick,
16
+ }) => {
17
+ const { activeValue, onSelect, registerRef } = useSegments()
18
+ const isActive: boolean = isActiveProp ?? value === activeValue
19
+
9
20
  const segmentButtonClasses: string = getClasses(styles.segmentButton, {
10
21
  [styles.segmentButtonActive]: isActive,
11
22
  })
12
23
 
13
24
  const iconL: HeliosIconType | undefined = iconLeft || icon
14
25
 
26
+ const handleClick = useCallback((): void => {
27
+ onSelect(value)
28
+ onClick?.()
29
+ }, [value, onSelect, onClick])
30
+
31
+ const handleRef = useCallback(
32
+ (el: HTMLButtonElement | null): void => {
33
+ registerRef(value, el)
34
+ },
35
+ [value, registerRef],
36
+ )
37
+
15
38
  return (
16
- <button ref={ref} className={segmentButtonClasses} onClick={onClick} data-ui-component="SegmentButton">
39
+ <button ref={handleRef} className={segmentButtonClasses} onClick={handleClick} data-ui-component="SegmentButton">
17
40
  {iconL && <Icon icon={iconL} size={16} />}
18
41
  {value}
19
42
  {iconRight && <Icon icon={iconRight} size={16} />}
20
43
  </button>
21
44
  )
22
45
  }
46
+
47
+ SegmentButton.displayName = "SegmentButton"
@@ -1,4 +1,3 @@
1
- import type { Ref } from "react"
2
1
  import type { HeliosIconType } from "../../../../types/icons"
3
2
 
4
3
  export interface SegmentButtonProps {
@@ -7,6 +6,5 @@ export interface SegmentButtonProps {
7
6
  iconRight?: HeliosIconType
8
7
  isActive?: boolean
9
8
  onClick?: () => void
10
- ref?: Ref<HTMLButtonElement>
11
9
  value: string
12
10
  }
@@ -3,54 +3,51 @@
3
3
  import { Flex } from "../Flex"
4
4
  import { Icon } from "../Icon"
5
5
  import { getClasses } from "@heliosgraphics/utils"
6
- import { useId, type FC } from "react"
6
+ import { useId, forwardRef } from "react"
7
7
  import { InputLabel } from "../shared/InputLabel"
8
8
  import styles from "./Select.module.css"
9
9
  import type { SelectProps } from "./Select.types"
10
10
 
11
- export const Select: FC<SelectProps> = ({
12
- selectedValue,
13
- onChange,
14
- isLabelHidden,
15
- isSmall,
16
- isDisabled,
17
- isRequired,
18
- items,
19
- label,
20
- }) => {
21
- const htmlFor: string = useId()
11
+ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
12
+ ({ selectedValue, onChange, isLabelHidden, isSmall, isDisabled, isRequired, items, label }, ref) => {
13
+ const htmlFor: string = useId()
22
14
 
23
- const selectClasses: string = getClasses(styles.select, {
24
- [styles.selectDisabled]: isDisabled,
25
- [styles.selectSmall]: isSmall,
26
- [styles.selectHiddenLabel]: !label || isLabelHidden,
27
- })
15
+ const selectClasses: string = getClasses(styles.select, {
16
+ [styles.selectDisabled]: isDisabled,
17
+ [styles.selectSmall]: isSmall,
18
+ [styles.selectHiddenLabel]: !label || isLabelHidden,
19
+ })
28
20
 
29
- return (
30
- <Flex isColumn={true} className={selectClasses} data-ui-component="Select">
31
- <InputLabel label={label} id={htmlFor} isHidden={!!isLabelHidden} isDisabled={!!isDisabled} />
32
- <Flex>
33
- <select
34
- className={styles.select__select}
35
- onChange={onChange}
36
- id={htmlFor}
37
- value={selectedValue}
38
- disabled={isDisabled}
39
- required={isRequired}
40
- aria-label={isLabelHidden ? label : undefined}
41
- >
42
- {items?.map((item) => {
43
- return (
44
- <option key={item.value} value={item.value} disabled={item.isDisabled}>
45
- {item.name}
46
- </option>
47
- )
48
- })}
49
- </select>
50
- <Flex className={styles.select__icon} isCentered={true}>
51
- <Icon icon="chevron-down" size={20} />
21
+ return (
22
+ <Flex isColumn={true} className={selectClasses} data-ui-component="Select">
23
+ <InputLabel label={label} id={htmlFor} isHidden={!!isLabelHidden} isDisabled={!!isDisabled} />
24
+ <Flex>
25
+ <select
26
+ ref={ref}
27
+ className={styles.select__select}
28
+ onChange={onChange}
29
+ id={htmlFor}
30
+ value={selectedValue}
31
+ disabled={isDisabled}
32
+ required={isRequired}
33
+ aria-label={isLabelHidden ? label : undefined}
34
+ aria-required={isRequired || undefined}
35
+ >
36
+ {items?.map((item) => {
37
+ return (
38
+ <option key={item.value} value={item.value} disabled={item.isDisabled}>
39
+ {item.name}
40
+ </option>
41
+ )
42
+ })}
43
+ </select>
44
+ <Flex className={styles.select__icon} isCentered={true}>
45
+ <Icon icon="chevron-down" size={20} />
46
+ </Flex>
52
47
  </Flex>
53
48
  </Flex>
54
- </Flex>
55
- )
56
- }
49
+ )
50
+ },
51
+ )
52
+
53
+ Select.displayName = "Select"
@@ -32,3 +32,5 @@ export const Separator: FC<SeparatorProps> = ({
32
32
 
33
33
  return <HorizontalSeparator {...separatorProps} />
34
34
  }
35
+
36
+ Separator.displayName = "Separator"
@@ -6,3 +6,5 @@ export const HRMarkup: FC<HRMarkupProps> = (props) => {
6
6
 
7
7
  return <hr {...goodProps} data-ui-component="Separator" />
8
8
  }
9
+
10
+ HRMarkup.displayName = "HRMarkup"
@@ -18,3 +18,5 @@ export const HorizontalSeparator: FC<HorizontalSeparatorProps> = ({ lineStyle, c
18
18
 
19
19
  return <HRMarkup className={hrClassNames} style={horizontalStyle} />
20
20
  }
21
+
22
+ HorizontalSeparator.displayName = "HorizontalSeparator"