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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/components/Alert/Alert.tsx +1 -1
  2. package/components/Breadcrumb/Breadcrumb.meta.ts +1 -1
  3. package/components/Breadcrumb/Breadcrumb.tsx +4 -4
  4. package/components/Browser/Browser.tsx +6 -2
  5. package/components/Button/Button.tsx +175 -173
  6. package/components/ButtonGroup/ButtonGroup.tsx +2 -4
  7. package/components/Checkbox/Checkbox.tsx +61 -55
  8. package/components/Clock/Clock.module.css +1 -1
  9. package/components/Clock/Clock.tsx +1 -1
  10. package/components/Column/Column.tsx +2 -2
  11. package/components/Confirm/Confirm.tsx +1 -1
  12. package/components/DatePicker/DatePicker.tsx +6 -2
  13. package/components/Debug/Debug.tsx +2 -2
  14. package/components/Details/Details.tsx +1 -1
  15. package/components/Dialog/Dialog.tsx +32 -5
  16. package/components/Donut/Donut.module.css +1 -1
  17. package/components/Donut/Donut.tsx +8 -1
  18. package/components/Dot/Dot.tsx +1 -1
  19. package/components/Dropdown/Dropdown.tsx +46 -19
  20. package/components/Fieldset/Fieldset.tsx +1 -1
  21. package/components/Flex/Flex.tsx +12 -10
  22. package/components/Grid/Grid.tsx +7 -3
  23. package/components/Heading/Heading.tsx +15 -10
  24. package/components/Heading/components/H0/H0.tsx +1 -1
  25. package/components/Icon/Icon.tsx +1 -1
  26. package/components/Input/Input.tsx +13 -3
  27. package/components/Layout/components/LayoutAside/LayoutAside.tsx +1 -1
  28. package/components/Layout/components/LayoutAside/components/LayoutAsideContent/LayoutAsideContent.tsx +1 -1
  29. package/components/Layout/components/LayoutAside/components/LayoutAsideFooter/LayoutAsideFooter.tsx +1 -1
  30. package/components/Layout/components/LayoutAside/components/LayoutAsideToggle/LayoutAsideToggle.tsx +1 -1
  31. package/components/Layout/components/LayoutMain/LayoutMain.tsx +1 -1
  32. package/components/Layout/components/LayoutMain/components/LayoutMainContent/LayoutMainContent.tsx +1 -1
  33. package/components/Layout/components/LayoutNavigation/LayoutNavigation.tsx +1 -1
  34. package/components/Loading/Loading.tsx +10 -2
  35. package/components/Markdown/Markdown.meta.ts +7 -0
  36. package/components/Markdown/Markdown.tsx +15 -6
  37. package/components/Markdown/Markdown.types.ts +4 -1
  38. package/components/Markdown/Markdown.utils.spec.ts +3 -3
  39. package/components/Markdown/Markdown.utils.ts +9 -7
  40. package/components/Masonry/Masonry.meta.ts +1 -1
  41. package/components/Masonry/Masonry.tsx +2 -2
  42. package/components/Masonry/Masonry.types.ts +1 -1
  43. package/components/Menu/Menu.tsx +1 -3
  44. package/components/Menu/components/MenuCategory/MenuCategory.tsx +13 -2
  45. package/components/Menu/components/MenuFilter/MenuFilter.tsx +1 -1
  46. package/components/Menu/components/MenuItem/MenuItem.tsx +14 -1
  47. package/components/Overlay/Overlay.tsx +1 -1
  48. package/components/Pie/Pie.tsx +7 -4
  49. package/components/Pill/Pill.tsx +1 -1
  50. package/components/Placeholder/Placeholder.tsx +1 -0
  51. package/components/Progress/Progress.tsx +9 -1
  52. package/components/Radio/Radio.tsx +64 -58
  53. package/components/Segments/Segments.tsx +6 -6
  54. package/components/Select/Select.tsx +5 -4
  55. package/components/Separator/components/HorizontalSeparator/HorizontalSeparator.tsx +1 -1
  56. package/components/Separator/components/VerticalSeparator/VerticalSeparator.tsx +1 -1
  57. package/{components.css → components/Setup/Setup.components.css} +2 -2
  58. package/components/Setup/Setup.meta.ts +2 -2
  59. package/components/Setup/Setup.tsx +10 -7
  60. package/components/Setup/Setup.types.ts +1 -0
  61. package/components/Shimmer/Shimmer.tsx +3 -2
  62. package/components/Slider/Slider.tsx +4 -4
  63. package/components/Spacer/Spacer.tsx +1 -1
  64. package/components/Table/Table.tsx +1 -1
  65. package/components/Tabs/Tabs.tsx +36 -12
  66. package/components/Text/Text.tsx +5 -2
  67. package/components/Textarea/Textarea.tsx +10 -5
  68. package/components/Tile/Tile.tsx +2 -9
  69. package/components/Tile/Tile.types.ts +1 -1
  70. package/components/Timestamp/Timestamp.meta.ts +5 -1
  71. package/components/Timestamp/Timestamp.tsx +3 -3
  72. package/components/Timestamp/Timestamp.types.ts +1 -1
  73. package/components/Toggle/Toggle.tsx +2 -2
  74. package/components/Tooltip/Tooltip.module.css +32 -0
  75. package/components/Tooltip/Tooltip.tsx +36 -72
  76. package/components/Tooltip/Tooltip.types.ts +1 -0
  77. package/components/Tooltip/components/TooltipContent/TooltipContent.tsx +5 -3
  78. package/components/Tooltip/components/TooltipTrigger/TooltipTrigger.tsx +10 -4
  79. package/components/shared/InputLabel/InputLabel.tsx +1 -1
  80. package/components/shared/ResultList/ResultList.tsx +6 -7
  81. package/constants/hooks.ts +1 -1
  82. package/contexts/LayoutContext/LayoutContext.tsx +30 -20
  83. package/contexts/LayoutContext/LayoutContext.types.ts +1 -0
  84. package/hooks/useDebounce.tsx +2 -2
  85. package/hooks/useLayoutContext.tsx +1 -0
  86. package/hooks/usePrint.tsx +31 -0
  87. package/index.ts +4 -0
  88. package/package.json +20 -12
  89. package/utils/date.ts +54 -0
  90. package/utils/dayjs.ts +0 -21
@@ -3,7 +3,7 @@ import type { PropsWithChildren } from "react"
3
3
  export interface MasonryBaseProps {
4
4
  columns: [number, number, number]
5
5
  gap: [number, number, number]
6
- brakepoints: [number, number, number]
6
+ breakpoints: [number, number, number]
7
7
  useBalancedLayout?: boolean
8
8
  }
9
9
 
@@ -1,6 +1,4 @@
1
- "use client"
2
-
3
- import { FC } from "react"
1
+ import type { FC } from "react"
4
2
  import type { MenuProps } from "./Menu.types"
5
3
  import { getClasses } from "@heliosgraphics/utils"
6
4
  import { getFlexUtility, getSafeFlexProps } from "../Flex/Flex.utils"
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import { useState, type FC } from "react"
3
+ import { useCallback, useState, type FC, type KeyboardEvent } from "react"
4
4
  import { Flex } from "../../../Flex"
5
5
  import { Icon } from "../../../Icon"
6
6
  import { Text } from "../../../Text"
@@ -26,6 +26,16 @@ export const MenuCategory: FC<MenuCategoryProps> = ({
26
26
  }
27
27
  }
28
28
 
29
+ const handleKeyDown = useCallback(
30
+ (event: KeyboardEvent<HTMLDivElement>): void => {
31
+ if (event.key === "Enter" || event.key === " ") {
32
+ event.preventDefault()
33
+ onToggle()
34
+ }
35
+ },
36
+ [isControlled],
37
+ )
38
+
29
39
  const menuCategoryClasses: string = getClasses(styles.menuCategory, {
30
40
  [styles.menuCategoryFolder]: category,
31
41
  [styles.menuCategoryFolderOpen]: isOpen,
@@ -46,10 +56,11 @@ export const MenuCategory: FC<MenuCategoryProps> = ({
46
56
  <Flex isColumn={true} isXCentered={true} data-ui-component="Menu.Category" className={menuCategoryClasses}>
47
57
  {showHeader && (
48
58
  <Flex
49
- {...(isFolder && onToggle && { onClick: onToggle })}
59
+ {...(isFolder && { onClick: onToggle, onKeyDown: handleKeyDown, role: "button" as const, tabIndex: 0 })}
50
60
  isBetween={true}
51
61
  isYCentered={true}
52
62
  gap={4}
63
+ aria-expanded={isFolder ? isOpen : undefined}
53
64
  className={styles.menuCategoryTitle}
54
65
  >
55
66
  <Text type="tiny" emphasis={isOpen ? "secondary" : "primary"} fontWeight="medium" isNonSelectable={true}>
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import { FC } from "react"
3
+ import type { FC } from "react"
4
4
  import { Input } from "../../../Input"
5
5
  import { getClasses } from "@heliosgraphics/utils"
6
6
  import styles from "./MenuFilter.module.css"
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import { FC } from "react"
3
+ import { useCallback, type FC, type KeyboardEvent } from "react"
4
4
  import { Flex } from "../../../Flex"
5
5
  import { Pill } from "../../../Pill"
6
6
  import { Icon } from "../../../Icon"
@@ -24,11 +24,24 @@ export const MenuItem: FC<MenuItemProps> = ({
24
24
  [styles.menuItemActive]: isActive,
25
25
  })
26
26
 
27
+ const handleKeyDown = useCallback(
28
+ (event: KeyboardEvent<HTMLDivElement>): void => {
29
+ if (event.key === "Enter" || event.key === " ") {
30
+ event.preventDefault()
31
+ onAsideClose()
32
+ }
33
+ },
34
+ [onAsideClose],
35
+ )
36
+
27
37
  return (
28
38
  <Flex
29
39
  className={menuItemClasses}
30
40
  data-ui-component="MenuItem"
41
+ role="menuitem"
42
+ tabIndex={0}
31
43
  onClick={onAsideClose}
44
+ onKeyDown={handleKeyDown}
32
45
  isBetween={true}
33
46
  gap={4}
34
47
  isYCentered={true}
@@ -18,7 +18,7 @@ export const Overlay: FC<OverlayProps> = ({ onClose, isCentered, children, isOpe
18
18
  })
19
19
 
20
20
  return (
21
- <section className={styles.overlay}>
21
+ <section className={styles.overlay} data-ui-component="Overlay">
22
22
  <div className={contentClasses}>{children}</div>
23
23
  <div className={styles.overlay__layer} onClick={hideFunction} />
24
24
  </section>
@@ -15,15 +15,18 @@ export const Pie: FC<PieProps> = ({ color, size, data }) => {
15
15
  width={size}
16
16
  viewBox={`0 0 ${size} ${size}`}
17
17
  style={{ backgroundColor: backgroundColor }}
18
+ role="img"
19
+ aria-label={`Pie chart with ${data.length} segments`}
20
+ data-ui-component="Pie"
18
21
  >
19
- {data.map((item, key) => {
20
- const thisData: Array<PieItem> = data.slice(0, key)
22
+ {data.map((item, index) => {
23
+ const thisData: Array<PieItem> = data.slice(0, index)
21
24
  const thisSize = thisData?.reduce((a, b) => a + b.value, 0)
22
- const thisColor: string = `hsl(var(--${color}-hue), ${(key + 1) * 20}%, 50%)`
25
+ const thisColor: string = `hsl(var(--${color}-hue), ${(index + 1) * 20}%, 50%)`
23
26
 
24
27
  return (
25
28
  <circle
26
- key={key}
29
+ key={item.name ?? `segment-${index}`}
27
30
  r={size / 4}
28
31
  cx={size / 2}
29
32
  cy={size / 2}
@@ -1,4 +1,4 @@
1
- import { getClasses } from "@heliosgraphics/utils/classnames"
1
+ import { getClasses } from "@heliosgraphics/utils"
2
2
  import { getColorClasses } from "../../utils/colors"
3
3
  import { Flex } from "../Flex"
4
4
  import { Text } from "../Text"
@@ -9,6 +9,7 @@ export const Placeholder: FC<PlaceholderProps> = ({ children, height }) => {
9
9
  <Flex
10
10
  className={styles.placeholder}
11
11
  padding={16}
12
+ data-ui-component="Placeholder"
12
13
  style={{
13
14
  height: height ? `${height}px` : "inherit",
14
15
  }}
@@ -10,5 +10,13 @@ export const Progress: FC<ProgressProps> = ({ color = "blue", isSmall, max = 100
10
10
  [styles.progressSmall]: isSmall,
11
11
  })
12
12
 
13
- return <progress className={progressClasses} max={max} value={value} />
13
+ return (
14
+ <progress
15
+ className={progressClasses}
16
+ max={max}
17
+ value={value}
18
+ aria-label={`Progress: ${value} of ${max}`}
19
+ data-ui-component="Progress"
20
+ />
21
+ )
14
22
  }
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
 
3
- import { useId } from "react"
4
- import { getClasses } from "@heliosgraphics/utils/classnames"
3
+ import { useId, memo } from "react"
4
+ import { getClasses } from "@heliosgraphics/utils"
5
5
  import { getColorClasses } from "../../utils/colors"
6
6
  import { Flex } from "../Flex"
7
7
  import { Text } from "../Text"
@@ -9,63 +9,69 @@ import styles from "./Radio.module.css"
9
9
  import type { FC } from "react"
10
10
  import type { RadioProps } from "./Radio.types"
11
11
 
12
- export const Radio: FC<RadioProps> = ({
13
- color = "gray",
14
- isChecked,
15
- isVertical,
16
- isLabelHidden = false,
17
- isSmall,
18
- description,
19
- isDisabled,
20
- isRequired,
21
- onChange,
22
- label,
23
- name,
24
- value,
25
- }) => {
26
- const radioId: string = useId()
12
+ export const Radio: FC<RadioProps> = memo(
13
+ ({
14
+ color = "gray",
15
+ isChecked,
16
+ isVertical,
17
+ isLabelHidden = false,
18
+ isSmall,
19
+ description,
20
+ isDisabled,
21
+ isRequired,
22
+ onChange,
23
+ label,
24
+ name,
25
+ value,
26
+ }) => {
27
+ const radioId: string = useId()
27
28
 
28
- const colorClasses = getColorClasses(color, "dark")
29
- const radioClasses = getClasses(styles.radio, ...colorClasses, {
30
- [styles.radioDisabled]: isDisabled,
31
- [styles.radioSmall]: isSmall,
32
- })
29
+ const colorClasses = getColorClasses(color, "dark")
30
+ const radioClasses = getClasses(styles.radio, ...colorClasses, {
31
+ [styles.radioDisabled]: isDisabled,
32
+ [styles.radioSmall]: isSmall,
33
+ })
33
34
 
34
- const radioLabelClasses = getClasses(styles.radio__radioLabel, "flex gap-4", {
35
- "flex-x-center flex-column": isVertical,
36
- "flex-y-center": !isVertical,
37
- })
35
+ const radioLabelClasses = getClasses(styles.radio__radioLabel, "flex gap-4", {
36
+ "flex-x-center flex-column": isVertical,
37
+ "flex-y-center": !isVertical,
38
+ })
38
39
 
39
- return (
40
- <div className={radioClasses}>
41
- <label className={radioLabelClasses} htmlFor={radioId}>
42
- <span className={styles.radio__mark}>
43
- <input
44
- type="radio"
45
- checked={isChecked}
46
- onChange={onChange}
47
- disabled={isDisabled}
48
- required={isRequired}
49
- aria-label={isLabelHidden ? label : undefined}
50
- id={radioId}
51
- name={name}
52
- value={value}
53
- />
54
- <div className={styles.radio__radioMark} />
55
- </span>
56
- {!isLabelHidden && (
57
- <Flex isColumn={true}>
58
- <Text type={isSmall ? "tiny" : "small"} fontWeight="medium" emphasis={isDisabled ? "tertiary" : "inherit"}>
59
- {label}
60
- </Text>
61
- {description && (
62
- <Text type="tiny" emphasis="secondary">
63
- {description}
40
+ return (
41
+ <div className={radioClasses} data-ui-component="Radio">
42
+ <label className={radioLabelClasses} htmlFor={radioId}>
43
+ <span className={styles.radio__mark}>
44
+ <input
45
+ type="radio"
46
+ checked={isChecked}
47
+ onChange={onChange}
48
+ disabled={isDisabled}
49
+ required={isRequired}
50
+ aria-label={isLabelHidden ? label : undefined}
51
+ id={radioId}
52
+ name={name}
53
+ value={value}
54
+ />
55
+ <div className={styles.radio__radioMark} />
56
+ </span>
57
+ {!isLabelHidden && (
58
+ <Flex isColumn={true}>
59
+ <Text
60
+ type={isSmall ? "tiny" : "small"}
61
+ fontWeight="medium"
62
+ emphasis={isDisabled ? "tertiary" : "inherit"}
63
+ >
64
+ {label}
64
65
  </Text>
65
- )}
66
- </Flex>
67
- )}
68
- </label>
69
- </div>
70
- )
71
- }
66
+ {description && (
67
+ <Text type="tiny" emphasis="secondary">
68
+ {description}
69
+ </Text>
70
+ )}
71
+ </Flex>
72
+ )}
73
+ </label>
74
+ </div>
75
+ )
76
+ },
77
+ )
@@ -6,6 +6,7 @@ import {
6
6
  useState,
7
7
  useRef,
8
8
  useEffect,
9
+ useCallback,
9
10
  Children,
10
11
  isValidElement,
11
12
  cloneElement,
@@ -35,7 +36,7 @@ export const Segments: FC<SegmentsProps> = ({ children, isFullWidth, isSmall })
35
36
  const containerRef = useRef<HTMLDivElement>(null)
36
37
  const sliderRef = useRef<HTMLDivElement>(null)
37
38
 
38
- const updateSliderPosition = (): void => {
39
+ const updateSliderPosition = useCallback((): void => {
39
40
  if (!containerRef.current || !sliderRef.current || !activeValue) return
40
41
 
41
42
  const activeIndex: number = validChildren.findIndex((child) => child.props.value === activeValue)
@@ -52,19 +53,18 @@ export const Segments: FC<SegmentsProps> = ({ children, isFullWidth, isSmall })
52
53
  sliderRef.current.style.transform = `translateX(${left}px)`
53
54
  sliderRef.current.style.width = `${width}px`
54
55
  sliderRef.current.style.opacity = "1"
55
- }
56
+ }, [activeValue, validChildren])
56
57
 
57
58
  useEffect(() => {
58
59
  const timer: ReturnType<typeof setTimeout> = setTimeout(updateSliderPosition, 0)
59
- const handleResize = (): void => updateSliderPosition()
60
60
 
61
- globalThis.addEventListener("resize", handleResize)
61
+ globalThis.addEventListener("resize", updateSliderPosition)
62
62
 
63
63
  return (): void => {
64
64
  globalThis.clearTimeout(timer)
65
- globalThis.removeEventListener("resize", handleResize)
65
+ globalThis.removeEventListener("resize", updateSliderPosition)
66
66
  }
67
- }, [activeValue])
67
+ }, [updateSliderPosition])
68
68
 
69
69
  const segmentedControlClasses: string = getClasses(styles.segmentedControl, {
70
70
  [styles.segmentedControlSmall]: isSmall,
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { Flex } from "../Flex"
4
4
  import { Icon } from "../Icon"
5
- import { getClasses } from "@heliosgraphics/utils/classnames"
5
+ import { getClasses } from "@heliosgraphics/utils"
6
6
  import { useId, type FC } from "react"
7
7
  import { InputLabel } from "../shared/InputLabel"
8
8
  import styles from "./Select.module.css"
@@ -26,7 +26,7 @@ export const Select: FC<SelectProps> = ({
26
26
  })
27
27
 
28
28
  return (
29
- <Flex isColumn={true} className={selectClasses}>
29
+ <Flex isColumn={true} className={selectClasses} data-ui-component="Select">
30
30
  <InputLabel label={label} id={htmlFor} isHidden={!!isLabelHidden} isDisabled={!!isDisabled} />
31
31
  <Flex>
32
32
  <select
@@ -34,11 +34,12 @@ export const Select: FC<SelectProps> = ({
34
34
  onChange={onChange}
35
35
  id={htmlFor}
36
36
  value={selectedValue}
37
+ disabled={isDisabled}
37
38
  aria-label={isLabelHidden ? label : undefined}
38
39
  >
39
- {items?.map((item, key) => {
40
+ {items?.map((item) => {
40
41
  return (
41
- <option key={key} value={item.value} disabled={item.isDisabled}>
42
+ <option key={item.value} value={item.value} disabled={item.isDisabled}>
42
43
  {item.name}
43
44
  </option>
44
45
  )
@@ -1,4 +1,4 @@
1
- import { getClasses } from "@heliosgraphics/utils/classnames"
1
+ import { getClasses } from "@heliosgraphics/utils"
2
2
  import { HRMarkup } from "../HRMarkup"
3
3
  import styles from "./HorizontalSeparator.module.css"
4
4
  import type { FC } from "react"
@@ -1,4 +1,4 @@
1
- import { getClasses } from "@heliosgraphics/utils/classnames"
1
+ import { getClasses } from "@heliosgraphics/utils"
2
2
  import { HRMarkup } from "../HRMarkup"
3
3
  import styles from "./VerticalSeparator.module.css"
4
4
  import type { FC } from "react"
@@ -1,7 +1,7 @@
1
1
  @layer helios;
2
2
 
3
- @import "@heliosgraphics/css/dist/css.min.layered.css";
4
- @import "@heliosgraphics/css/dist/css.easings.layered.css";
3
+ @import "@heliosgraphics/css/layered";
4
+ @import "@heliosgraphics/css/easings/layered";
5
5
 
6
6
  @import "./css/core.colors.css";
7
7
  @import "./css/core.colors-helpers.css";
@@ -12,11 +12,11 @@ export const meta: HeliosAttributeMeta<SetupProps> = {
12
12
  _status: "nominal",
13
13
  _category: "meta",
14
14
  fixedTheme: {
15
- type: "HeliosThemeType",
15
+ type: "HeliosFixedThemeType",
16
16
  isOptional: true,
17
17
  },
18
18
  theme: {
19
- type: "HeliosFixedThemeType",
19
+ type: "HeliosThemeType",
20
20
  isOptional: true,
21
21
  },
22
22
  }
@@ -1,4 +1,4 @@
1
- import "../../components.css"
1
+ import "./Setup.components.css"
2
2
 
3
3
  import type { FC } from "react"
4
4
  import type { HeliosFixedThemeType } from "../../types/themes"
@@ -19,13 +19,15 @@ declare global {
19
19
 
20
20
  export const Setup: FC<SetupProps> = ({ theme = "system", fixedTheme }) => {
21
21
  const preconnectLinks: Array<SetupLinkProps> = [
22
- { rel: "preconnect", href: "https://rsms.me/", crossOrigin: "anonymous" },
22
+ { id: "rsms", rel: "preconnect", href: "https://rsms.me/", crossOrigin: "anonymous" },
23
23
  {
24
+ id: "googleapis",
24
25
  rel: "preconnect",
25
26
  href: "https://fonts.googleapis.com",
26
27
  crossOrigin: "anonymous",
27
28
  },
28
29
  {
30
+ id: "gstatic",
29
31
  rel: "preconnect",
30
32
  href: "https://fonts.gstatic.com",
31
33
  crossOrigin: "anonymous",
@@ -33,8 +35,9 @@ export const Setup: FC<SetupProps> = ({ theme = "system", fixedTheme }) => {
33
35
  ]
34
36
 
35
37
  const stylesheetLinks: Array<SetupLinkProps> = [
36
- { rel: "stylesheet", href: "https://rsms.me/inter/inter.css" },
38
+ { id: "rsms", rel: "stylesheet", href: "https://rsms.me/inter/inter.css" },
37
39
  {
40
+ id: "googleapis",
38
41
  rel: "stylesheet",
39
42
  href: "https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap",
40
43
  },
@@ -44,11 +47,11 @@ export const Setup: FC<SetupProps> = ({ theme = "system", fixedTheme }) => {
44
47
 
45
48
  return (
46
49
  <>
47
- {preconnectLinks.map((linkProps, index) => (
48
- <link key={`preconnect-${index}`} {...linkProps} />
50
+ {preconnectLinks.map((linkProps) => (
51
+ <link key={`preconnect-${linkProps.id}`} {...linkProps} />
49
52
  ))}
50
- {stylesheetLinks.map((linkProps, index) => (
51
- <link key={`stylesheet-${index}`} {...linkProps} />
53
+ {stylesheetLinks.map((linkProps) => (
54
+ <link key={`stylesheet-${linkProps.id}`} {...linkProps} />
52
55
  ))}
53
56
  <script
54
57
  dangerouslySetInnerHTML={{
@@ -6,6 +6,7 @@ export interface SetupProps {
6
6
  }
7
7
 
8
8
  export interface SetupLinkProps {
9
+ id: string
9
10
  rel: string
10
11
  href: string
11
12
  crossOrigin?: "anonymous" | "use-credentials"
@@ -1,5 +1,5 @@
1
- import { ShimmerProps } from "./Shimmer.types"
2
- import { getClasses } from "@heliosgraphics/utils/classnames"
1
+ import type { ShimmerProps } from "./Shimmer.types"
2
+ import { getClasses } from "@heliosgraphics/utils"
3
3
  import styles from "./Shimmer.module.css"
4
4
  import type { FC } from "react"
5
5
 
@@ -12,6 +12,7 @@ export const Shimmer: FC<ShimmerProps> = ({ isRounded, paddingTop, paddingBottom
12
12
  return (
13
13
  <div
14
14
  className={styles.shimmer}
15
+ data-ui-component="Shimmer"
15
16
  role="status"
16
17
  aria-busy={true}
17
18
  aria-label="Loading"
@@ -1,4 +1,4 @@
1
- import { getClasses } from "@heliosgraphics/utils/classnames"
1
+ import { getClasses } from "@heliosgraphics/utils"
2
2
  import styles from "./Slider.module.css"
3
3
  import type { FC } from "react"
4
4
  import type { SliderProps } from "./Slider.types"
@@ -7,9 +7,9 @@ export const Slider: FC<SliderProps> = ({ items }) => {
7
7
  const sliderClasses: string = getClasses(styles.slider, "flex")
8
8
 
9
9
  return (
10
- <ul className={sliderClasses}>
11
- {items?.map((item, key) => (
12
- <li key={key}>{item}</li>
10
+ <ul className={sliderClasses} data-ui-component="Slider">
11
+ {items?.map((item, index) => (
12
+ <li key={`slider-${index}`}>{item}</li>
13
13
  ))}
14
14
  </ul>
15
15
  )
@@ -2,5 +2,5 @@ import { memo, type FC } from "react"
2
2
  import type { SpacerProps } from "./Spacer.types"
3
3
 
4
4
  export const Spacer: FC<SpacerProps> = memo(({ gap }) => {
5
- return <div style={{ height: `${gap ?? 0}px` }} />
5
+ return <div style={{ height: `${gap ?? 0}px` }} data-ui-component="Spacer" />
6
6
  })
@@ -12,7 +12,7 @@ export const Table: FC<TableProps> = ({ children, hasBorder, isMonoHeader }) =>
12
12
  })
13
13
 
14
14
  return (
15
- <div className={tableClasses}>
15
+ <div className={tableClasses} data-ui-component="Table">
16
16
  <table className={tableElementClasses}>{children}</table>
17
17
  </div>
18
18
  )
@@ -1,31 +1,55 @@
1
1
  "use client"
2
2
 
3
- import { useState, type FC } from "react"
4
- import { TabsProps } from "./Tabs.types"
3
+ import { useId, useState, type FC, type KeyboardEvent } from "react"
4
+ import type { TabsProps } from "./Tabs.types"
5
5
  import styles from "./Tabs.module.css"
6
6
  import { Text } from "../Text"
7
7
 
8
8
  export const Tabs: FC<TabsProps> = ({ active: activeNumber, items, sections }) => {
9
9
  const [active, setActive] = useState(activeNumber || 0)
10
+ const tabsId: string = useId()
10
11
 
11
12
  if (!items || !sections) return null
12
13
 
14
+ const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>, index: number): void => {
15
+ if (event.key === "ArrowRight") {
16
+ event.preventDefault()
17
+ setActive((index + 1) % items.length)
18
+ } else if (event.key === "ArrowLeft") {
19
+ event.preventDefault()
20
+ setActive((index - 1 + items.length) % items.length)
21
+ }
22
+ }
23
+
13
24
  return (
14
- <div>
15
- <ol className={`${styles.tabs__ol} flex gap-2`}>
16
- {items.map((tab, key) => (
17
- <li
18
- key={key}
19
- onClick={() => setActive(key)}
20
- className={`${styles.tabs__ol__item} ${active === key ? styles.tabs__ol__itemActive : ""}`}
25
+ <div data-ui-component="Tabs">
26
+ <div role="tablist" className={`${styles.tabs__ol} flex gap-2`}>
27
+ {items.map((tab, index) => (
28
+ <div
29
+ key={tab}
30
+ role="tab"
31
+ tabIndex={active === index ? 0 : -1}
32
+ aria-selected={active === index}
33
+ aria-controls={`${tabsId}-panel-${index}`}
34
+ id={`${tabsId}-tab-${index}`}
35
+ onClick={() => setActive(index)}
36
+ onKeyDown={(event) => handleKeyDown(event, index)}
37
+ className={`${styles.tabs__ol__item} ${active === index ? styles.tabs__ol__itemActive : ""}`}
21
38
  >
22
39
  <Text type="small" fontWeight="medium">
23
40
  {tab}
24
41
  </Text>
25
- </li>
42
+ </div>
26
43
  ))}
27
- </ol>
28
- <section className="tabs__section">{sections[active]}</section>
44
+ </div>
45
+ <section
46
+ role="tabpanel"
47
+ id={`${tabsId}-panel-${active}`}
48
+ aria-labelledby={`${tabsId}-tab-${active}`}
49
+ className="tabs__section"
50
+ >
51
+ {sections[active]}
52
+ </section>
29
53
  </div>
30
54
  )
31
55
  }
@@ -1,5 +1,5 @@
1
1
  import { Div } from "./components/Div"
2
- import { getClasses } from "@heliosgraphics/utils/classnames"
2
+ import { getClasses } from "@heliosgraphics/utils"
3
3
  import { getTypographyUtility } from "./Text.utils"
4
4
  import { P } from "./components/P"
5
5
  import { Small } from "./components/Small"
@@ -30,10 +30,13 @@ export const Text: FC<TextProps> = memo((props) => {
30
30
  }
31
31
  : undefined
32
32
 
33
+ const mergedStyle: object | undefined =
34
+ props.style || lineClampStyle ? { ...(props.style || {}), ...(lineClampStyle || {}) } : undefined
35
+
33
36
  const baseTextProps: Omit<TextProps, "type"> = {
34
37
  onClick: props.onClick,
35
38
  children: props.children,
36
- style: lineClampStyle,
39
+ style: mergedStyle,
37
40
  className: utility,
38
41
  }
39
42