@heliosgraphics/ui 2.0.0-alpha.88 → 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 +3 -3
  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
@@ -6,7 +6,7 @@ import type { FC } from "react"
6
6
 
7
7
  export const Details: FC<DetailsProps> = ({ children, label, isOpen }) => {
8
8
  return (
9
- <details className={styles.details} open={isOpen}>
9
+ <details className={styles.details} open={isOpen} data-ui-component="Details">
10
10
  <summary className="flex space-between flex-y-center">
11
11
  <Text type="small" fontWeight="medium">
12
12
  {label}
@@ -1,12 +1,12 @@
1
1
  "use client"
2
2
 
3
- import { useEffect, useRef, type FC, MouseEvent } from "react"
3
+ import { useEffect, useRef, type FC, type MouseEvent } from "react"
4
4
  import styles from "./Dialog.module.css"
5
5
  import { Button } from "../Button"
6
6
  import { ButtonGroup } from "../ButtonGroup"
7
7
  import { Heading } from "../Heading"
8
8
  import { Flex } from "../Flex"
9
- import { getClasses } from "@heliosgraphics/utils/classnames"
9
+ import { getClasses } from "@heliosgraphics/utils"
10
10
  import type { DialogProps } from "./Dialog.types"
11
11
 
12
12
  const ATTRIBUTE_SCROLL = "data-scroll" as const
@@ -14,6 +14,7 @@ const POSITION_FIXED_CLASS = "fixed" as const
14
14
 
15
15
  export const Dialog: FC<DialogProps> = ({ title, children, isNarrow, isOpen, isCentered, onClose, noPadding }) => {
16
16
  const dialogRef = useRef<HTMLDialogElement | null>(null)
17
+ const triggerRef = useRef<Element | null>(null)
17
18
 
18
19
  const resetScroll = (): void => {
19
20
  globalThis?.requestAnimationFrame(() => {
@@ -35,15 +36,20 @@ export const Dialog: FC<DialogProps> = ({ title, children, isNarrow, isOpen, isC
35
36
 
36
37
  useEffect(() => {
37
38
  return (): void => {
38
- resetDocumentStyle(0)
39
+ const localPos: string = document?.body?.getAttribute(ATTRIBUTE_SCROLL) ?? "0"
40
+ const scrollPosition: number = parseInt(localPos) ?? 0
41
+
42
+ resetDocumentStyle(scrollPosition)
39
43
  resetScroll()
40
44
  }
41
- }, [title])
45
+ }, [title, isOpen])
42
46
 
43
47
  useEffect(() => {
44
48
  dialogRef?.current?.scrollTo?.(0, 0)
45
49
 
46
50
  if (isOpen) {
51
+ triggerRef.current = document.activeElement
52
+
47
53
  const localPosition: number = document?.documentElement?.scrollTop ?? 0
48
54
 
49
55
  document.body.classList.add(POSITION_FIXED_CLASS)
@@ -51,6 +57,25 @@ export const Dialog: FC<DialogProps> = ({ title, children, isNarrow, isOpen, isC
51
57
  document.body.setAttribute(ATTRIBUTE_SCROLL, localPosition.toString())
52
58
 
53
59
  dialogRef?.current?.showModal?.()
60
+
61
+ const dialog = dialogRef.current
62
+
63
+ const handleCancel = (event: Event): void => {
64
+ event.preventDefault()
65
+ onClose()
66
+ }
67
+
68
+ dialog?.addEventListener("cancel", handleCancel)
69
+
70
+ return (): void => {
71
+ dialog?.removeEventListener("cancel", handleCancel)
72
+
73
+ const trigger = triggerRef.current
74
+ if (trigger && trigger instanceof HTMLElement) {
75
+ trigger.focus()
76
+ }
77
+ triggerRef.current = null
78
+ }
54
79
  } else if (!dialogRef?.current) {
55
80
  const localPos: string = document?.body?.getAttribute(ATTRIBUTE_SCROLL) ?? "0"
56
81
  const scrollPosition: number = parseInt(localPos) ?? 0
@@ -61,6 +86,8 @@ export const Dialog: FC<DialogProps> = ({ title, children, isNarrow, isOpen, isC
61
86
 
62
87
  dialogRef?.current?.close?.()
63
88
  }
89
+
90
+ return undefined
64
91
  }, [isOpen])
65
92
 
66
93
  if (!isOpen) return null
@@ -81,7 +108,7 @@ export const Dialog: FC<DialogProps> = ({ title, children, isNarrow, isOpen, isC
81
108
  })
82
109
 
83
110
  return (
84
- <dialog ref={dialogRef} className={dialogClasses} onClick={onDialogClose}>
111
+ <dialog ref={dialogRef} className={dialogClasses} onClick={onDialogClose} data-ui-component="Dialog">
85
112
  {!!title && (
86
113
  <Flex isBetween={true} isYCentered={true} padding={8} className={dialogFlexClasses}>
87
114
  <Heading level={5} fontWeight="medium">
@@ -4,5 +4,5 @@
4
4
  }
5
5
 
6
6
  .donut .donut__svg circle {
7
- transition: all var(--speed-sm) var(--ease-in-out-sine);
7
+ transition: stroke-dashoffset var(--speed-sm) var(--ease-in-out-sine);
8
8
  }
@@ -18,7 +18,12 @@ export const Donut: FC<DonutProps> = ({ children, size, percentage = 0, color })
18
18
  }
19
19
 
20
20
  return (
21
- <Flex style={donutContainerStyle} className={`${styles.donut} relative`} isCentered={true}>
21
+ <Flex
22
+ style={donutContainerStyle}
23
+ className={`${styles.donut} relative`}
24
+ isCentered={true}
25
+ data-ui-component="Donut"
26
+ >
22
27
  <Flex className="absolute top-0 left-0 z-50" style={donutContainerStyle} isCentered={true}>
23
28
  {children}
24
29
  </Flex>
@@ -28,6 +33,8 @@ export const Donut: FC<DonutProps> = ({ children, size, percentage = 0, color })
28
33
  viewBox={`0 0 ${size} ${size}`}
29
34
  height={size}
30
35
  width={size}
36
+ role="img"
37
+ aria-label={`Donut chart: ${percentage}%`}
31
38
  >
32
39
  <circle
33
40
  fill="none"
@@ -25,7 +25,7 @@ export const Dot: FC<DotProps> = ({ colorAccent, size = 8, color = "blue" }) =>
25
25
  }
26
26
 
27
27
  return (
28
- <div className={styles.dot} style={dotStyle}>
28
+ <div className={styles.dot} style={dotStyle} data-ui-component="Dot">
29
29
  <svg width="100%" height="100%" viewBox="0 0 20 20" aria-hidden={true}>
30
30
  {colorAccent ? (
31
31
  <>
@@ -1,18 +1,27 @@
1
1
  "use client"
2
2
 
3
- import { useEffect, useState, useRef, cloneElement, Children as ReactChildren, type ReactElement, type FC } from "react"
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"
4
13
  import { ANIMATION_FAST } from "../../constants/animations"
5
- import { getClasses } from "@heliosgraphics/utils/classnames"
14
+ import { getClasses } from "@heliosgraphics/utils"
6
15
  import { ResultList } from "../shared/ResultList"
7
16
  import styles from "./Dropdown.module.css"
8
17
  import type { MouseEvent, ReactNode } from "react"
9
18
  import type { DropdownProps } from "./Dropdown.types"
10
19
  import type { HeliosIconType } from "../../types/icons"
11
20
 
12
- // NOTE @03b8 instead of passing ref, should use https://www.w3.org/TR/css-anchor-position-1/ when ready
13
21
  export const Dropdown: FC<DropdownProps> = ({ children, items, isDisabled, position = "bottom-left" }) => {
14
22
  const hoverStateRef = useRef<boolean>(false)
15
23
  const resultListRef = useRef<HTMLOListElement | null>(null)
24
+ const hideTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
16
25
 
17
26
  const [isVisible, setVisible] = useState<boolean>(false)
18
27
  const [isHovering, setHovering] = useState<boolean>(false)
@@ -24,6 +33,12 @@ export const Dropdown: FC<DropdownProps> = ({ children, items, isDisabled, posit
24
33
  }
25
34
  }, [isVisible, items?.length])
26
35
 
36
+ useEffect(() => {
37
+ return (): void => {
38
+ if (hideTimeoutRef.current) globalThis.clearTimeout(hideTimeoutRef.current)
39
+ }
40
+ }, [])
41
+
27
42
  hoverStateRef.current = isHovering
28
43
 
29
44
  if (isDisabled) {
@@ -37,11 +52,19 @@ export const Dropdown: FC<DropdownProps> = ({ children, items, isDisabled, posit
37
52
  [styles.dropdownTopRight]: position === "top-right",
38
53
  })
39
54
 
40
- const mouseEnter = (_event: MouseEvent<HTMLDivElement>): void => setHovering(true)
41
- const mouseLeave = (_event: MouseEvent<HTMLDivElement>): ReturnType<typeof setTimeout> => {
55
+ const mouseEnter = (_event: MouseEvent<HTMLDivElement>): void => {
56
+ if (hideTimeoutRef.current) {
57
+ globalThis.clearTimeout(hideTimeoutRef.current)
58
+ hideTimeoutRef.current = null
59
+ }
60
+ setHovering(true)
61
+ }
62
+ const mouseLeave = (_event: MouseEvent<HTMLDivElement>): void => {
42
63
  setHovering(false)
43
-
44
- return setTimeout(() => !hoverStateRef.current && setVisible(false), ANIMATION_FAST)
64
+ hideTimeoutRef.current = setTimeout(() => {
65
+ if (!hoverStateRef.current) setVisible(false)
66
+ hideTimeoutRef.current = null
67
+ }, ANIMATION_FAST)
45
68
  }
46
69
 
47
70
  const renderChildren = ReactChildren.map(
@@ -62,20 +85,24 @@ export const Dropdown: FC<DropdownProps> = ({ children, items, isDisabled, posit
62
85
  [styles.dropdown__navActive]: isVisible,
63
86
  })
64
87
 
65
- const itemsWithClose = items?.map((item) => {
66
- if (item.isDisabled) return item
67
-
68
- return {
69
- ...item,
70
- onClick: (event: MouseEvent<HTMLElement>): void => {
71
- item.onClick?.(event)
72
- setVisible(false)
73
- },
74
- }
75
- })
88
+ const itemsWithClose = useMemo(
89
+ () =>
90
+ items?.map((item) => {
91
+ if (item.isDisabled) return item
92
+
93
+ return {
94
+ ...item,
95
+ onClick: (event: MouseEvent<HTMLElement>): void => {
96
+ item.onClick?.(event)
97
+ setVisible(false)
98
+ },
99
+ }
100
+ }),
101
+ [items],
102
+ )
76
103
 
77
104
  return (
78
- <div className={dropdownClasses} onMouseEnter={mouseEnter} onMouseLeave={mouseLeave}>
105
+ <div className={dropdownClasses} onMouseEnter={mouseEnter} onMouseLeave={mouseLeave} data-ui-component="Dropdown">
79
106
  <div onClick={onSetVisible} aria-expanded={isVisible} aria-haspopup="listbox">
80
107
  {renderChildren}
81
108
  </div>
@@ -4,7 +4,7 @@ import type { FieldsetProps } from "./Fieldset.types"
4
4
 
5
5
  export const Fieldset: FC<FieldsetProps> = ({ children, legend }) => {
6
6
  return (
7
- <fieldset className={styles.fieldset}>
7
+ <fieldset className={styles.fieldset} data-ui-component="Fieldset">
8
8
  <legend>{legend}</legend>
9
9
  {children}
10
10
  </fieldset>
@@ -1,14 +1,16 @@
1
- import { forwardRef, type ForwardedRef } from "react"
1
+ import { forwardRef, memo, type ForwardedRef } from "react"
2
2
  import { getFlexUtility, getSafeFlexProps } from "../Flex/Flex.utils"
3
3
  import type { FlexProps } from "./Flex.types"
4
4
 
5
- export const Flex = forwardRef<HTMLDivElement, FlexProps>((props: FlexProps, ref: ForwardedRef<HTMLDivElement>) => {
6
- const flexClasses: string = getFlexUtility(props)
7
- const safeProps = getSafeFlexProps(props)
5
+ export const Flex = memo(
6
+ forwardRef<HTMLDivElement, FlexProps>((props: FlexProps, ref: ForwardedRef<HTMLDivElement>) => {
7
+ const flexClasses: string = getFlexUtility(props)
8
+ const safeProps = getSafeFlexProps(props)
8
9
 
9
- return (
10
- <div {...safeProps} className={flexClasses} onClick={props.onClick} ref={ref}>
11
- {props.children}
12
- </div>
13
- )
14
- })
10
+ return (
11
+ <div {...safeProps} className={flexClasses} onClick={props.onClick} ref={ref}>
12
+ {props.children}
13
+ </div>
14
+ )
15
+ }),
16
+ )
@@ -1,5 +1,5 @@
1
- import { GridProps } from "./Grid.types"
2
- import { getClasses } from "@heliosgraphics/utils/classnames"
1
+ import type { GridProps } from "./Grid.types"
2
+ import { getClasses } from "@heliosgraphics/utils"
3
3
  import type { FC } from "react"
4
4
 
5
5
  export const Grid: FC<GridProps> = ({ columns, children }) => {
@@ -7,5 +7,9 @@ export const Grid: FC<GridProps> = ({ columns, children }) => {
7
7
  [`grid-cols-${columns}`]: columns,
8
8
  })
9
9
 
10
- return <div className={gridClasses}>{children}</div>
10
+ return (
11
+ <div className={gridClasses} data-ui-component="Grid">
12
+ {children}
13
+ </div>
14
+ )
11
15
  }
@@ -1,5 +1,5 @@
1
1
  import { getTypographyUtility } from "../Text/Text.utils"
2
- import { getClasses } from "@heliosgraphics/utils/classnames"
2
+ import { getClasses } from "@heliosgraphics/utils"
3
3
  import { H0 } from "./components/H0"
4
4
  import { H1 } from "./components/H1"
5
5
  import { H2 } from "./components/H2"
@@ -9,10 +9,12 @@ import { H5 } from "./components/H5"
9
9
  import { H6 } from "./components/H6"
10
10
  import styles from "./Heading.module.css"
11
11
  import type { HeadingProps } from "./Heading.types"
12
- import type { FC, CSSProperties } from "react"
12
+ import { memo, type FC, type CSSProperties } from "react"
13
13
 
14
- export const Heading: FC<HeadingProps> = (props) => {
15
- const headingClasses: string = getClasses(props.className, styles.heading, {
14
+ export const Heading: FC<HeadingProps> = memo((props) => {
15
+ const { level, lineClamp, style, className, ...rest } = props
16
+
17
+ const headingClasses: string = getClasses(className, styles.heading, {
16
18
  [styles.headingPrimary]: props.emphasis === "primary",
17
19
  [styles.headingSecondary]: props.emphasis === "secondary",
18
20
  [styles.headingTertiary]: props.emphasis === "tertiary",
@@ -21,23 +23,26 @@ export const Heading: FC<HeadingProps> = (props) => {
21
23
  ...props,
22
24
  className: headingClasses,
23
25
  })
24
- const lineClampStyle: CSSProperties | undefined = props.lineClamp
26
+ const lineClampStyle: CSSProperties | undefined = lineClamp
25
27
  ? {
26
28
  display: "-webkit-box",
27
- WebkitLineClamp: props.lineClamp,
29
+ WebkitLineClamp: lineClamp,
28
30
  WebkitBoxOrient: "vertical",
29
31
  overflow: "hidden",
30
32
  }
31
33
  : undefined
32
34
 
35
+ const mergedStyle: CSSProperties | undefined =
36
+ style || lineClampStyle ? { ...(style || {}), ...(lineClampStyle || {}) } : undefined
37
+
33
38
  const allProps: Omit<HeadingProps, "level"> = {
34
- onClick: props.onClick,
39
+ ...rest,
35
40
  children: props.children,
36
- style: lineClampStyle,
41
+ style: mergedStyle,
37
42
  className: utility,
38
43
  }
39
44
 
40
- switch (props.level) {
45
+ switch (level) {
41
46
  case 0:
42
47
  return <H0 {...allProps} />
43
48
  case 1:
@@ -55,4 +60,4 @@ export const Heading: FC<HeadingProps> = (props) => {
55
60
  default:
56
61
  return null
57
62
  }
58
- }
63
+ })
@@ -1,4 +1,4 @@
1
- import { getClasses } from "@heliosgraphics/utils/classnames"
1
+ import { getClasses } from "@heliosgraphics/utils"
2
2
  import styles from "./H0.module.css"
3
3
  import type { FC } from "react"
4
4
  import type { H0Props } from "./H0.types"
@@ -1,4 +1,4 @@
1
- import { getClasses } from "@heliosgraphics/utils/classnames"
1
+ import { getClasses } from "@heliosgraphics/utils"
2
2
  import { icons } from "@heliosgraphics/icons"
3
3
  import styles from "./Icon.module.css"
4
4
  import { memo, type FC } from "react"
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import { useId, type FC } from "react"
4
- import { getClasses } from "@heliosgraphics/utils/classnames"
4
+ import { getClasses } from "@heliosgraphics/utils"
5
5
  import { Button } from "../Button"
6
6
  import { ButtonGroup } from "../ButtonGroup"
7
7
  import { Loading } from "../Loading"
@@ -22,10 +22,14 @@ export const Input: FC<InputProps> = ({
22
22
  isLoading,
23
23
  isRequired,
24
24
  label,
25
+ maxLength,
26
+ name,
25
27
  onBlur,
26
28
  onChange,
27
29
  onClear,
28
30
  onFocus,
31
+ onKeyDown,
32
+ onKeyUp,
29
33
  placeholder,
30
34
  results,
31
35
  showResults,
@@ -33,6 +37,7 @@ export const Input: FC<InputProps> = ({
33
37
  value,
34
38
  }) => {
35
39
  const htmlFor: string = useId()
40
+ const helperId: string = `${htmlFor}-helper`
36
41
 
37
42
  const filteredItems: Array<ResultItem> =
38
43
  results
@@ -52,7 +57,7 @@ export const Input: FC<InputProps> = ({
52
57
  })
53
58
 
54
59
  return (
55
- <div className={inputClasses}>
60
+ <div className={inputClasses} data-ui-component="Input">
56
61
  <InputLabel id={htmlFor} label={label} isDisabled={!!isDisabled} isHidden={!!isLabelHidden} />
57
62
  <Flex className="grow-1">
58
63
  {onClear && !!value && (
@@ -74,13 +79,18 @@ export const Input: FC<InputProps> = ({
74
79
  autoComplete={autoComplete}
75
80
  autoFocus={autoFocus}
76
81
  id={htmlFor}
82
+ maxLength={maxLength}
83
+ name={name}
77
84
  onBlur={onBlur}
78
85
  type={type}
79
86
  onChange={onChange}
80
87
  onFocus={onFocus}
88
+ onKeyDown={onKeyDown}
89
+ onKeyUp={onKeyUp}
81
90
  placeholder={placeholder}
82
91
  required={isRequired}
83
92
  value={value}
93
+ aria-describedby={helperText ? helperId : undefined}
84
94
  />
85
95
  {isLoading && (
86
96
  <div className={styles.input__loading}>
@@ -94,7 +104,7 @@ export const Input: FC<InputProps> = ({
94
104
  </div>
95
105
  )}
96
106
  {!!helperText && (
97
- <Text type="tiny" emphasis="tertiary" className={styles.input__helper}>
107
+ <Text type="tiny" emphasis="tertiary" className={styles.input__helper} id={helperId}>
98
108
  {helperText}
99
109
  </Text>
100
110
  )}
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import { getClasses } from "@heliosgraphics/utils/classnames"
3
+ import { getClasses } from "@heliosgraphics/utils"
4
4
  import { useLayoutContext } from "../../../../hooks/useLayoutContext"
5
5
  import styles from "./LayoutAside.module.css"
6
6
  import type { LayoutAsideProps } from "./LayoutAside.types"
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import { getClasses } from "@heliosgraphics/utils/classnames"
3
+ import { getClasses } from "@heliosgraphics/utils"
4
4
  import { getFlexUtility, getSafeFlexProps } from "../../../../../Flex/Flex.utils"
5
5
  import styles from "./LayoutAsideContent.module.css"
6
6
  import type { LayoutAsideContentProps } from "./LayoutAsideContent.types"
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import { getClasses } from "@heliosgraphics/utils/classnames"
3
+ import { getClasses } from "@heliosgraphics/utils"
4
4
  import { getFlexUtility, getSafeFlexProps } from "../../../../../Flex/Flex.utils"
5
5
  import styles from "./LayoutAsideFooter.module.css"
6
6
  import type { LayoutAsideFooterProps } from "./LayoutAsideFooter.types"
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import { FC } from "react"
3
+ import type { FC } from "react"
4
4
  import { Button } from "../../../../../Button/Button"
5
5
  import { ButtonGroup } from "../../../../../ButtonGroup/ButtonGroup"
6
6
  import { useLayoutContext } from "../../../../../../hooks/useLayoutContext"
@@ -1,4 +1,4 @@
1
- import { getClasses } from "@heliosgraphics/utils/classnames"
1
+ import { getClasses } from "@heliosgraphics/utils"
2
2
  import { getFlexUtility, getSafeFlexProps } from "../../../Flex/Flex.utils"
3
3
  import styles from "./LayoutMain.module.css"
4
4
  import type { FC } from "react"
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import { getFlexUtility, getSafeFlexProps } from "../../../../../Flex/Flex.utils"
4
- import { getClasses } from "@heliosgraphics/utils/classnames"
4
+ import { getClasses } from "@heliosgraphics/utils"
5
5
  import styles from "./LayoutMainContent.module.css"
6
6
  import { useLayoutEffect, useRef, type FC } from "react"
7
7
  import {
@@ -1,5 +1,5 @@
1
1
  import { getFlexUtility, getSafeFlexProps } from "../../../Flex/Flex.utils"
2
- import { getClasses } from "@heliosgraphics/utils/classnames"
2
+ import { getClasses } from "@heliosgraphics/utils"
3
3
  import styles from "./LayoutNavigation.module.css"
4
4
  import type { FC } from "react"
5
5
  import type { LayoutNavigationProps } from "./LayoutNavigation.types"
@@ -1,4 +1,4 @@
1
- import { getClasses } from "@heliosgraphics/utils/classnames"
1
+ import { getClasses } from "@heliosgraphics/utils"
2
2
  import styles from "./Loading.module.css"
3
3
  import { memo, type FC } from "react"
4
4
  import type { LoadingProps } from "./Loading.types"
@@ -18,7 +18,15 @@ export const Loading: FC<LoadingProps> = memo(({ className, size, emphasis }) =>
18
18
  })
19
19
 
20
20
  return (
21
- <svg className={loadingClasses} xmlns="http://www.w3.org/2000/svg" height={size + 4} width={size + 4}>
21
+ <svg
22
+ className={loadingClasses}
23
+ xmlns="http://www.w3.org/2000/svg"
24
+ height={size + 4}
25
+ width={size + 4}
26
+ role="status"
27
+ aria-label="Loading"
28
+ data-ui-component="Loading"
29
+ >
22
30
  <circle
23
31
  fill="none"
24
32
  strokeWidth={4}
@@ -11,11 +11,18 @@ export const meta: HeliosAttributeMeta<MarkdownProps> = {
11
11
  ],
12
12
  _status: "nominal",
13
13
  _category: "content",
14
+ children: {
15
+ type: "ReactNode",
16
+ isOptional: true,
17
+ description: "Children to render inside the markdown wrapper",
18
+ },
14
19
  isNonSelectable: {
15
20
  type: "boolean",
16
21
  isOptional: true,
17
22
  },
18
23
  text: {
19
24
  type: "string",
25
+ isOptional: true,
26
+ description: "Markdown string to render as HTML",
20
27
  },
21
28
  }
@@ -1,16 +1,25 @@
1
- import { md } from "./Markdown.utils"
2
- import { getClasses } from "@heliosgraphics/utils/classnames"
1
+ import { renderMarkdown } from "./Markdown.utils"
2
+ import { getClasses } from "@heliosgraphics/utils"
3
3
  import styles from "./Markdown.module.css"
4
4
  import type { FC } from "react"
5
5
  import type { MarkdownProps } from "./Markdown.types"
6
6
 
7
- export const Markdown: FC<MarkdownProps> = ({ text, isNonSelectable }) => {
8
- if (!text) return null
7
+ export const Markdown: FC<MarkdownProps> = ({ text, children, isNonSelectable }) => {
8
+ if (!text && !children) return null
9
9
 
10
- const innerHTML = { __html: md.render(text) }
11
10
  const markdownClasses: string = getClasses(styles.markdown, {
12
11
  [styles.markdownNonSelectable]: isNonSelectable,
13
12
  })
14
13
 
15
- return <div className={markdownClasses} dangerouslySetInnerHTML={innerHTML}></div>
14
+ if (text) {
15
+ const innerHTML = { __html: renderMarkdown(text) }
16
+
17
+ return <div className={markdownClasses} dangerouslySetInnerHTML={innerHTML} data-ui-component="Markdown"></div>
18
+ }
19
+
20
+ return (
21
+ <div className={markdownClasses} data-ui-component="Markdown">
22
+ {children}
23
+ </div>
24
+ )
16
25
  }
@@ -1,4 +1,7 @@
1
+ import type { ReactNode } from "react"
2
+
1
3
  export interface MarkdownProps {
4
+ children?: ReactNode
2
5
  isNonSelectable?: boolean
3
- text: string
6
+ text?: string
4
7
  }
@@ -1,11 +1,11 @@
1
1
  import { it, describe, expect } from "vitest"
2
- import { md } from "./Markdown.utils"
2
+ import { renderMarkdown } from "./Markdown.utils"
3
3
 
4
4
  describe("markdown", () => {
5
- describe("md", () => {
5
+ describe("renderMarkdown", () => {
6
6
  const SAMPLE = `Hello\n\nHey`
7
7
  const SAMPLE_OUTPUT = `<p>Hello</p>\n<p>Hey</p>\n`
8
8
 
9
- it("Returns", () => expect(md.render(SAMPLE)).toEqual(SAMPLE_OUTPUT))
9
+ it("Returns", () => expect(renderMarkdown(SAMPLE)).toEqual(SAMPLE_OUTPUT))
10
10
  })
11
11
  })
@@ -1,8 +1,10 @@
1
- import markdownIt from "markdown-it"
1
+ import { Marked } from "marked"
2
+ import { markedXhtml } from "marked-xhtml"
3
+ import markedLinkifyIt from "marked-linkify-it"
2
4
 
3
- export const md = markdownIt({
4
- breaks: true,
5
- html: true,
6
- xhtmlOut: true,
7
- linkify: true,
8
- })
5
+ const marked = new Marked({ breaks: true, gfm: true })
6
+
7
+ marked.use(markedXhtml())
8
+ marked.use(markedLinkifyIt())
9
+
10
+ export const renderMarkdown = (text: string): string => marked.parse(text) as string
@@ -12,7 +12,7 @@ export const meta: HeliosAttributeMeta<MasonryBaseProps> = {
12
12
  ],
13
13
  _status: "experimental",
14
14
  _category: "layout",
15
- brakepoints: {
15
+ breakpoints: {
16
16
  type: "[number, number, number]",
17
17
  },
18
18
  columns: {