@heliosgraphics/ui 2.0.0-alpha.93 → 2.0.0-alpha.94

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 (56) hide show
  1. package/components/Breadcrumb/Breadcrumb.tsx +25 -23
  2. package/components/Button/Button.tsx +172 -174
  3. package/components/Checkbox/Checkbox.tsx +54 -60
  4. package/components/Confirm/Confirm.tsx +1 -1
  5. package/components/Dialog/Dialog.meta.ts +4 -0
  6. package/components/Dialog/Dialog.tsx +11 -2
  7. package/components/Dialog/Dialog.types.ts +1 -0
  8. package/components/Donut/Donut.tsx +1 -0
  9. package/components/Dropdown/Dropdown.tsx +6 -1
  10. package/components/Flex/Flex.meta.ts +2 -0
  11. package/components/Flex/Flex.tsx +2 -3
  12. package/components/Flex/Flex.types.ts +3 -1
  13. package/components/Flex/Flex.utils.ts +36 -1
  14. package/components/Flex/index.ts +0 -2
  15. package/components/Heading/Heading.tsx +3 -3
  16. package/components/Icon/Icon.tsx +3 -3
  17. package/components/Input/Input.tsx +5 -2
  18. package/components/Layout/components/LayoutAside/components/LayoutAsideContent/LayoutAsideContent.tsx +0 -2
  19. package/components/Layout/components/LayoutAside/components/LayoutAsideFooter/LayoutAsideFooter.tsx +0 -2
  20. package/components/Loading/Loading.tsx +8 -4
  21. package/components/Masonry/Masonry.meta.ts +1 -5
  22. package/components/Masonry/Masonry.tsx +27 -14
  23. package/components/Masonry/Masonry.types.ts +4 -4
  24. package/components/Overlay/Overlay.tsx +1 -1
  25. package/components/Pie/Pie.tsx +1 -0
  26. package/components/Progress/Progress.meta.ts +4 -0
  27. package/components/Progress/Progress.tsx +8 -2
  28. package/components/Progress/Progress.types.ts +1 -0
  29. package/components/Radio/Radio.tsx +57 -63
  30. package/components/Range/Range.meta.ts +26 -0
  31. package/components/Range/Range.module.css +68 -0
  32. package/components/Range/Range.tsx +47 -0
  33. package/components/Range/Range.types.ts +13 -0
  34. package/components/Range/index.ts +1 -0
  35. package/components/Select/Select.meta.ts +4 -0
  36. package/components/Select/Select.tsx +2 -0
  37. package/components/Select/Select.types.ts +1 -0
  38. package/components/Separator/Separator.tsx +25 -20
  39. package/components/Separator/components/VerticalSeparator/VerticalSeparator.tsx +1 -1
  40. package/components/Setup/Setup.tsx +0 -13
  41. package/components/Setup/css/feat.responsive.css +402 -0
  42. package/components/Slider/Slider.meta.ts +4 -0
  43. package/components/Slider/Slider.tsx +2 -2
  44. package/components/Slider/Slider.types.ts +1 -0
  45. package/components/Spacer/Spacer.tsx +3 -3
  46. package/components/Tabs/Tabs.tsx +14 -3
  47. package/components/Text/Text.tsx +3 -3
  48. package/components/Tile/Tile.tsx +10 -1
  49. package/components/Tooltip/Tooltip.tsx +3 -1
  50. package/components/Tooltip/Tooltip.types.ts +1 -0
  51. package/components/Tooltip/components/TooltipContent/TooltipContent.tsx +9 -2
  52. package/components/Tooltip/components/TooltipTrigger/TooltipTrigger.tsx +2 -2
  53. package/constants/components.ts +2 -0
  54. package/globals.d.ts +12 -0
  55. package/index.ts +1 -0
  56. package/package.json +6 -5
@@ -1,8 +1,7 @@
1
- import { memo } from "react"
2
1
  import { getFlexUtility, getSafeFlexProps } from "../Flex/Flex.utils"
3
2
  import type { FlexProps } from "./Flex.types"
4
3
 
5
- export const Flex = memo((props: FlexProps) => {
4
+ export const Flex = (props: FlexProps) => {
6
5
  const { ref, ...restProps } = props
7
6
  const flexClasses: string = getFlexUtility(restProps)
8
7
  const safeProps = getSafeFlexProps(restProps)
@@ -12,4 +11,4 @@ export const Flex = memo((props: FlexProps) => {
12
11
  {restProps.children}
13
12
  </div>
14
13
  )
15
- })
14
+ }
@@ -8,7 +8,7 @@ export type ResponsiveRadiusType = HeliosRadiusType | [HeliosRadiusType, HeliosR
8
8
  export interface FlexBaseProps {
9
9
  className?: string
10
10
  elevation?: "small" | "medium" | "large"
11
- gap?: HeliosScaleType
11
+ gap?: ResponsiveScaleType
12
12
  grow?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
13
13
  isAround?: boolean
14
14
  isBetween?: boolean
@@ -23,6 +23,8 @@ export interface FlexBaseProps {
23
23
  isYCentered?: boolean
24
24
  onClick?: MouseEventHandler<HTMLDivElement>
25
25
  padding?: ResponsiveScaleType
26
+ paddingBlock?: ResponsiveScaleType
27
+ paddingInline?: ResponsiveScaleType
26
28
  paddingX?: HeliosScaleType
27
29
  paddingY?: HeliosScaleType
28
30
  ref?: Ref<HTMLDivElement>
@@ -42,7 +42,21 @@ export const getFlexUtility = (props?: FlexProps): string => {
42
42
  flexClasses.add(responsivePadding)
43
43
  }
44
44
 
45
- if (props.gap) flexClasses.add(`gap-${props.gap}`)
45
+ if (props.gap) {
46
+ const responsiveGap: string = getResponsiveScale(props.gap, "gap")
47
+ flexClasses.add(responsiveGap)
48
+ }
49
+
50
+ if (props.paddingBlock) {
51
+ const responsivePaddingBlock: string = getResponsiveScale(props.paddingBlock, "py")
52
+ flexClasses.add(responsivePaddingBlock)
53
+ }
54
+
55
+ if (props.paddingInline) {
56
+ const responsivePaddingInline: string = getResponsiveScale(props.paddingInline, "px")
57
+ flexClasses.add(responsivePaddingInline)
58
+ }
59
+
46
60
  if (props.paddingY) flexClasses.add(`py-${props.paddingY}`)
47
61
  if (props.paddingX) flexClasses.add(`px-${props.paddingX}`)
48
62
 
@@ -54,6 +68,25 @@ export const getFlexUtility = (props?: FlexProps): string => {
54
68
  return Array.from(flexClasses).join(" ")
55
69
  }
56
70
 
71
+ export const getResponsiveScale = (value?: ResponsiveScaleType, prefix: string = "p"): string => {
72
+ if (!value) return ""
73
+
74
+ const isArray: boolean = Boolean(value && value instanceof Array)
75
+ const classes = new Set<string>()
76
+
77
+ if (!isArray) return `${prefix}-${value}`
78
+
79
+ for (let index: number = 0; index < 3; index++) {
80
+ const element = (value as [HeliosScaleType, HeliosScaleType, HeliosScaleType])[index]
81
+
82
+ if (index === 0) classes.add(`mobile:${prefix}-${element ?? 0}`)
83
+ if (index === 1) classes.add(`tablet:${prefix}-${element ?? 0}`)
84
+ if (index === 2) classes.add(`${prefix}-${element ?? 0}`)
85
+ }
86
+
87
+ return Array.from(classes).join(" ")
88
+ }
89
+
57
90
  export const getPadding = (paddingValue?: ResponsiveScaleType): string => {
58
91
  if (!paddingValue) return ""
59
92
 
@@ -111,6 +144,8 @@ export const getSafeFlexProps = (props: any): Partial<FlexProps> => {
111
144
  isXCentered: _isXCentered,
112
145
  isYCentered: _isYCentered,
113
146
  padding: _padding,
147
+ paddingBlock: _paddingBlock,
148
+ paddingInline: _paddingInline,
114
149
  paddingX: _paddingX,
115
150
  paddingY: _paddingY,
116
151
  withBackground: _withBackground,
@@ -1,6 +1,4 @@
1
1
  import { Flex } from "./Flex"
2
2
 
3
- Flex.displayName = "Flex"
4
-
5
3
  export { Flex }
6
4
  export type { FlexProps, ResponsiveRadiusType } from "./Flex.types"
@@ -9,9 +9,9 @@ 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 { memo, type FC, type CSSProperties } from "react"
12
+ import type { FC, CSSProperties } from "react"
13
13
 
14
- export const Heading: FC<HeadingProps> = memo((props) => {
14
+ export const Heading: FC<HeadingProps> = (props) => {
15
15
  const { level, lineClamp, style, className, ...rest } = props
16
16
 
17
17
  const headingClasses: string = getClasses(className, styles.heading, {
@@ -60,4 +60,4 @@ export const Heading: FC<HeadingProps> = memo((props) => {
60
60
  default:
61
61
  return null
62
62
  }
63
- })
63
+ }
@@ -1,10 +1,10 @@
1
1
  import { getClasses } from "@heliosgraphics/utils"
2
2
  import { icons } from "@heliosgraphics/icons"
3
3
  import styles from "./Icon.module.css"
4
- import { memo, type FC } from "react"
4
+ import type { FC } from "react"
5
5
  import type { IconProps } from "./Icon.types"
6
6
 
7
- export const Icon: FC<IconProps> = memo(({ icon, className, emphasis, size }) => {
7
+ export const Icon: FC<IconProps> = ({ icon, className, emphasis, size }) => {
8
8
  const iconSizeStyle = {
9
9
  height: size + "px",
10
10
  minHeight: size + "px",
@@ -27,4 +27,4 @@ export const Icon: FC<IconProps> = memo(({ icon, className, emphasis, size }) =>
27
27
  dangerouslySetInnerHTML={{ __html: icons[icon] || "" }}
28
28
  />
29
29
  )
30
- })
30
+ }
@@ -14,7 +14,9 @@ import type { InputProps } from "./Input.types"
14
14
  import type { ResultItem } from "../shared/ResultList"
15
15
 
16
16
  export const Input: FC<InputProps> = ({
17
+ className,
17
18
  helperText,
19
+ id,
18
20
  isDisabled,
19
21
  autoComplete,
20
22
  autoFocus,
@@ -36,7 +38,8 @@ export const Input: FC<InputProps> = ({
36
38
  type = "text",
37
39
  value,
38
40
  }) => {
39
- const htmlFor: string = useId()
41
+ const generatedId: string = useId()
42
+ const htmlFor: string = id || generatedId
40
43
  const helperId: string = `${htmlFor}-helper`
41
44
 
42
45
  const filteredItems: Array<ResultItem> =
@@ -51,7 +54,7 @@ export const Input: FC<InputProps> = ({
51
54
 
52
55
  const showingResults: boolean = Boolean(!!filteredItems?.length && showResults)
53
56
 
54
- const inputClasses: string = getClasses(styles.input, "relative flex flex-column", {
57
+ const inputClasses: string = getClasses(styles.input, "relative flex flex-column", className, {
55
58
  [styles.inputDisabled]: isDisabled,
56
59
  [styles.inputShowingResults]: showingResults,
57
60
  })
@@ -1,5 +1,3 @@
1
- "use client"
2
-
3
1
  import { getClasses } from "@heliosgraphics/utils"
4
2
  import { getFlexUtility, getSafeFlexProps } from "../../../../../Flex/Flex.utils"
5
3
  import styles from "./LayoutAsideContent.module.css"
@@ -1,5 +1,3 @@
1
- "use client"
2
-
3
1
  import { getClasses } from "@heliosgraphics/utils"
4
2
  import { getFlexUtility, getSafeFlexProps } from "../../../../../Flex/Flex.utils"
5
3
  import styles from "./LayoutAsideFooter.module.css"
@@ -1,9 +1,12 @@
1
+ "use client"
2
+
1
3
  import { getClasses } from "@heliosgraphics/utils"
4
+ import { useId, type FC } from "react"
2
5
  import styles from "./Loading.module.css"
3
- import { memo, type FC } from "react"
4
6
  import type { LoadingProps } from "./Loading.types"
5
7
 
6
- export const Loading: FC<LoadingProps> = memo(({ className, size, emphasis }) => {
8
+ export const Loading: FC<LoadingProps> = ({ className, size, emphasis }) => {
9
+ const titleId: string = useId()
7
10
  const rSize = size / 2
8
11
  const cSize = rSize + 2
9
12
  const dashSize = size + cSize
@@ -24,9 +27,10 @@ export const Loading: FC<LoadingProps> = memo(({ className, size, emphasis }) =>
24
27
  height={size + 4}
25
28
  width={size + 4}
26
29
  role="status"
27
- aria-label="Loading"
30
+ aria-labelledby={titleId}
28
31
  data-ui-component="Loading"
29
32
  >
33
+ <title id={titleId}>Loading</title>
30
34
  <circle
31
35
  fill="none"
32
36
  strokeWidth={4}
@@ -48,4 +52,4 @@ export const Loading: FC<LoadingProps> = memo(({ className, size, emphasis }) =>
48
52
  />
49
53
  </svg>
50
54
  )
51
- })
55
+ }
@@ -19,10 +19,6 @@ export const meta: HeliosAttributeMeta<MasonryBaseProps> = {
19
19
  type: "[number, number, number]",
20
20
  },
21
21
  gap: {
22
- type: "[number, number, number]",
23
- },
24
- useBalancedLayout: {
25
- type: "boolean",
26
- isOptional: true,
22
+ type: "[HeliosScaleType, HeliosScaleType, HeliosScaleType]",
27
23
  },
28
24
  }
@@ -1,24 +1,37 @@
1
1
  "use client"
2
2
 
3
- import { Masonry as MasonryPlock } from "react-plock"
4
- import { Children, type ReactElement, type FC } from "react"
3
+ import { type FC, useMemo } from "react"
5
4
  import type { MasonryProps } from "./Masonry.types"
6
5
 
7
- export const Masonry: FC<MasonryProps> = ({ children, columns, gap, breakpoints, useBalancedLayout }) => {
6
+ export const Masonry: FC<MasonryProps> = ({
7
+ children,
8
+ columns = [1, 2, 3],
9
+ gap = [2, 4, 6],
10
+ breakpoints = [0, 640, 960],
11
+ }) => {
12
+ const id: string = useMemo(() => `masonry-${Math.random().toString(36).slice(2, 8)}`, [])
13
+
8
14
  if (!children) return null
9
15
 
10
- const mappedChildren = Children.toArray(children).map((child) => <div className="Row">{child}</div>) as ReactElement[]
16
+ const mediaStyles = `
17
+ .${id} { column-count: ${columns[0]}; column-gap: ${gap[0]}px; }
18
+ .${id} > * { break-inside: avoid; margin-bottom: ${gap[0]}px; }
19
+
20
+ @media (min-width: ${breakpoints[1]}px) {
21
+ .${id} { column-count: ${columns[1]}; column-gap: ${gap[1]}px; }
22
+ .${id} > * { margin-bottom: ${gap[1]}px; }
23
+ }
24
+
25
+ @media (min-width: ${breakpoints[2]}px) {
26
+ .${id} { column-count: ${columns[2]}; column-gap: ${gap[2]}px; }
27
+ .${id} > * { margin-bottom: ${gap[2]}px; }
28
+ }
29
+ `
11
30
 
12
31
  return (
13
- <MasonryPlock
14
- items={mappedChildren}
15
- render={(item) => <>{item}</>}
16
- config={{
17
- columns: columns,
18
- gap: gap,
19
- media: breakpoints,
20
- useBalancedLayout: Boolean(useBalancedLayout),
21
- }}
22
- />
32
+ <>
33
+ <style dangerouslySetInnerHTML={{ __html: mediaStyles }} />
34
+ <div className={id}>{children}</div>
35
+ </>
23
36
  )
24
37
  }
@@ -1,10 +1,10 @@
1
1
  import type { PropsWithChildren } from "react"
2
+ import type { HeliosScaleType } from "../../types/scale"
2
3
 
3
4
  export interface MasonryBaseProps {
4
- columns: [number, number, number]
5
- gap: [number, number, number]
6
- breakpoints: [number, number, number]
7
- useBalancedLayout?: boolean
5
+ columns?: [number, number, number]
6
+ gap?: [HeliosScaleType, HeliosScaleType, HeliosScaleType]
7
+ breakpoints?: [number, number, number]
8
8
  }
9
9
 
10
10
  export type MasonryProps = PropsWithChildren<MasonryBaseProps>
@@ -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} data-ui-component="Overlay">
21
+ <section className={styles.overlay} role="dialog" aria-modal="true" data-ui-component="Overlay">
22
22
  <div className={contentClasses}>{children}</div>
23
23
  <div className={styles.overlay__layer} onClick={hideFunction} />
24
24
  </section>
@@ -19,6 +19,7 @@ export const Pie: FC<PieProps> = ({ color, size, data }) => {
19
19
  aria-label={`Pie chart with ${data.length} segments`}
20
20
  data-ui-component="Pie"
21
21
  >
22
+ <title>{data.map((item) => `${item.name}: ${item.value}`).join(", ")}</title>
22
23
  {data.map((item, index) => {
23
24
  const thisData: Array<PieItem> = data.slice(0, index)
24
25
  const thisSize = thisData?.reduce((a, b) => a + b.value, 0)
@@ -11,6 +11,10 @@ export const meta: HeliosAttributeMeta<ProgressProps> = {
11
11
  ],
12
12
  _status: "nominal",
13
13
  _category: "core",
14
+ "aria-label": {
15
+ type: "string",
16
+ isOptional: true,
17
+ },
14
18
  isSmall: {
15
19
  type: "boolean",
16
20
  isOptional: true,
@@ -4,7 +4,13 @@ import { getColorClasses } from "../../utils/colors"
4
4
  import type { FC } from "react"
5
5
  import type { ProgressProps } from "./Progress.types"
6
6
 
7
- export const Progress: FC<ProgressProps> = ({ color = "blue", isSmall, max = 100, value = 0 }) => {
7
+ export const Progress: FC<ProgressProps> = ({
8
+ "aria-label": ariaLabel,
9
+ color = "blue",
10
+ isSmall,
11
+ max = 100,
12
+ value = 0,
13
+ }) => {
8
14
  const progressColorClasses: Array<string> = getColorClasses(color, "dark")
9
15
  const progressClasses: string = getClasses(styles.progress, ...progressColorClasses, {
10
16
  [styles.progressSmall]: isSmall,
@@ -15,7 +21,7 @@ export const Progress: FC<ProgressProps> = ({ color = "blue", isSmall, max = 100
15
21
  className={progressClasses}
16
22
  max={max}
17
23
  value={value}
18
- aria-label={`Progress: ${value} of ${max}`}
24
+ aria-label={ariaLabel ?? `Progress: ${value} of ${max}`}
19
25
  data-ui-component="Progress"
20
26
  />
21
27
  )
@@ -1,6 +1,7 @@
1
1
  import type { HeliosColorType } from "../../types/colors"
2
2
 
3
3
  export interface ProgressProps {
4
+ "aria-label"?: string
4
5
  isSmall?: boolean
5
6
  color: HeliosColorType
6
7
  max: number
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import { useId, memo } from "react"
3
+ import { useId } from "react"
4
4
  import { getClasses } from "@heliosgraphics/utils"
5
5
  import { getColorClasses } from "../../utils/colors"
6
6
  import { Flex } from "../Flex"
@@ -9,69 +9,63 @@ 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> = 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()
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()
28
27
 
29
- const colorClasses = getColorClasses(color, "dark")
30
- const radioClasses = getClasses(styles.radio, ...colorClasses, {
31
- [styles.radioDisabled]: isDisabled,
32
- [styles.radioSmall]: isSmall,
33
- })
28
+ const colorClasses = getColorClasses(color, "dark")
29
+ const radioClasses = getClasses(styles.radio, ...colorClasses, {
30
+ [styles.radioDisabled]: isDisabled,
31
+ [styles.radioSmall]: isSmall,
32
+ })
34
33
 
35
- const radioLabelClasses = getClasses(styles.radio__radioLabel, "flex gap-4", {
36
- "flex-x-center flex-column": isVertical,
37
- "flex-y-center": !isVertical,
38
- })
34
+ const radioLabelClasses = getClasses(styles.radio__radioLabel, "flex gap-4", {
35
+ "flex-x-center flex-column": isVertical,
36
+ "flex-y-center": !isVertical,
37
+ })
39
38
 
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}
39
+ return (
40
+ <div className={radioClasses} data-ui-component="Radio">
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}
65
64
  </Text>
66
- {description && (
67
- <Text type="tiny" emphasis="secondary">
68
- {description}
69
- </Text>
70
- )}
71
- </Flex>
72
- )}
73
- </label>
74
- </div>
75
- )
76
- },
77
- )
65
+ )}
66
+ </Flex>
67
+ )}
68
+ </label>
69
+ </div>
70
+ )
71
+ }
@@ -0,0 +1,26 @@
1
+ import type { HeliosAttributeMeta } from "../../types/meta"
2
+ import type { RangeProps } from "./Range.types"
3
+
4
+ export const meta: HeliosAttributeMeta<RangeProps> = {
5
+ _patterns: [
6
+ {
7
+ id: "ui-range-default",
8
+ description: "default",
9
+ content: `<Range value={3} min={0} max={100} step={1} label={LABEL} onChange={FUNCTION} isDisabled={IS_DISABLED}/>`,
10
+ },
11
+ ],
12
+ _status: "experimental",
13
+ _category: "core",
14
+ isDisabled: { type: "boolean", isOptional: true },
15
+ isLabelHidden: { type: "boolean", isOptional: true },
16
+ label: { type: "string" },
17
+ max: { type: "number", isOptional: true, default: 100 },
18
+ min: { type: "number", isOptional: true, default: 0 },
19
+ name: { type: "string", isOptional: true },
20
+ onChange: {
21
+ type: "(event?: ChangeEvent<HTMLInputElement>) => unknown",
22
+ isOptional: true,
23
+ },
24
+ step: { type: "number", isOptional: true, default: 1 },
25
+ value: { type: "number", isOptional: true },
26
+ }
@@ -0,0 +1,68 @@
1
+ .range {
2
+ width: 100%;
3
+ }
4
+
5
+ .range__input {
6
+ width: 100%;
7
+ height: 4px;
8
+ margin: var(--space-3) 0;
9
+
10
+ appearance: none;
11
+ background: var(--ui-bg-soft-gray);
12
+ border-radius: var(--radius-sm);
13
+ outline: none;
14
+ transition: all var(--speed-sm) var(--ease-in-out-sine);
15
+ cursor: pointer;
16
+ }
17
+
18
+ .range__input::-webkit-slider-thumb {
19
+ appearance: none;
20
+ width: 18px;
21
+ height: 18px;
22
+
23
+ background: var(--ui-text-primary);
24
+ border: 2px solid var(--ui-bg-primary);
25
+ border-radius: var(--radius-round);
26
+ box-shadow: 0 0 0 1px var(--ui-border-primary);
27
+ cursor: pointer;
28
+ transition: all var(--speed-sm) var(--ease-in-out-sine);
29
+ }
30
+
31
+ .range__input::-moz-range-thumb {
32
+ width: 18px;
33
+ height: 18px;
34
+
35
+ background: var(--ui-text-primary);
36
+ border: 2px solid var(--ui-bg-primary);
37
+ border-radius: var(--radius-round);
38
+ box-shadow: 0 0 0 1px var(--ui-border-primary);
39
+ cursor: pointer;
40
+ transition: all var(--speed-sm) var(--ease-in-out-sine);
41
+ }
42
+
43
+ .range__input:focus::-webkit-slider-thumb {
44
+ box-shadow: 0 0 0 4px var(--ui-border-secondary);
45
+ }
46
+
47
+ .range__input:focus::-moz-range-thumb {
48
+ box-shadow: 0 0 0 4px var(--ui-border-secondary);
49
+ }
50
+
51
+ .range__input::-webkit-slider-runnable-track {
52
+ height: 4px;
53
+
54
+ background: var(--ui-bg-soft-gray);
55
+ border-radius: var(--radius-sm);
56
+ }
57
+
58
+ .range__input::-moz-range-track {
59
+ height: 4px;
60
+
61
+ background: var(--ui-bg-soft-gray);
62
+ border-radius: var(--radius-sm);
63
+ }
64
+
65
+ .rangeDisabled {
66
+ pointer-events: none;
67
+ opacity: 0.5;
68
+ }
@@ -0,0 +1,47 @@
1
+ "use client"
2
+
3
+ import { useId } from "react"
4
+ import { getClasses } from "@heliosgraphics/utils"
5
+ import { InputLabel } from "../shared/InputLabel"
6
+ import styles from "./Range.module.css"
7
+ import type { FC } from "react"
8
+ import type { RangeProps } from "./Range.types"
9
+
10
+ export const Range: FC<RangeProps> = ({
11
+ isDisabled,
12
+ isLabelHidden = false,
13
+ label,
14
+ max = 100,
15
+ min = 0,
16
+ name,
17
+ onChange,
18
+ step = 1,
19
+ value,
20
+ }) => {
21
+ const htmlFor: string = useId()
22
+
23
+ const rangeClasses: string = getClasses(styles.range, {
24
+ [styles.rangeDisabled]: isDisabled,
25
+ })
26
+
27
+ return (
28
+ <div className={rangeClasses} data-ui-component="Range">
29
+ <InputLabel id={htmlFor} label={label} isDisabled={!!isDisabled} isHidden={!!isLabelHidden} />
30
+ <input
31
+ className={styles.range__input}
32
+ disabled={isDisabled}
33
+ id={htmlFor}
34
+ max={max}
35
+ min={min}
36
+ name={name}
37
+ onChange={onChange}
38
+ step={step}
39
+ type="range"
40
+ value={value}
41
+ aria-valuemin={min}
42
+ aria-valuemax={max}
43
+ aria-valuenow={value}
44
+ />
45
+ </div>
46
+ )
47
+ }
@@ -0,0 +1,13 @@
1
+ import type { ChangeEventHandler } from "react"
2
+
3
+ export interface RangeProps {
4
+ isDisabled?: boolean
5
+ isLabelHidden?: boolean
6
+ label: string
7
+ max?: number
8
+ min?: number
9
+ name?: string
10
+ onChange?: ChangeEventHandler<HTMLInputElement>
11
+ step?: number
12
+ value?: number
13
+ }
@@ -0,0 +1 @@
1
+ export { Range } from "./Range"
@@ -32,6 +32,10 @@ export const meta: HeliosAttributeMeta<SelectProps> = {
32
32
  onChange: {
33
33
  type: "ChangeEventHandler<HTMLSelectElement>",
34
34
  },
35
+ isRequired: {
36
+ type: "boolean",
37
+ isOptional: true,
38
+ },
35
39
  selectedValue: {
36
40
  type: "string",
37
41
  isOptional: true,