@bspk/ui 1.3.9 → 1.3.11

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 (147) hide show
  1. package/dist/components/BannerAlert/BannerAlert.d.ts +5 -5
  2. package/dist/components/BannerAlert/BannerAlert.js +5 -5
  3. package/dist/components/Breadcrumb/BreadcrumbDropdown.d.ts +6 -0
  4. package/dist/components/Breadcrumb/BreadcrumbDropdown.js +6 -0
  5. package/dist/components/Breadcrumb/BreadcrumbDropdown.js.map +1 -1
  6. package/dist/components/CheckboxGroup/CheckboxGroupExample.js +1 -0
  7. package/dist/components/CheckboxGroup/CheckboxGroupExample.js.map +1 -1
  8. package/dist/components/ChipGroup/ChipGroup.d.ts +15 -28
  9. package/dist/components/ChipGroup/ChipGroup.js +12 -22
  10. package/dist/components/ChipGroup/ChipGroup.js.map +1 -1
  11. package/dist/components/ChipGroup/ChipGroupExample.js +61 -6
  12. package/dist/components/ChipGroup/ChipGroupExample.js.map +1 -1
  13. package/dist/components/ChipGroup/chip-group.css +5 -3
  14. package/dist/components/ChipGroup/chip-group.css.js +5 -3
  15. package/dist/components/Drawer/Drawer.js.map +1 -1
  16. package/dist/components/Field/FieldDescription.d.ts +7 -5
  17. package/dist/components/Field/FieldDescription.js +7 -3
  18. package/dist/components/Field/FieldDescription.js.map +1 -1
  19. package/dist/components/Field/FieldError.d.ts +6 -0
  20. package/dist/components/Field/FieldError.js +6 -0
  21. package/dist/components/Field/FieldError.js.map +1 -1
  22. package/dist/components/Field/FieldLabel.d.ts +6 -0
  23. package/dist/components/Field/FieldLabel.js +6 -0
  24. package/dist/components/Field/FieldLabel.js.map +1 -1
  25. package/dist/components/Field/utils.d.ts +5 -0
  26. package/dist/components/Field/utils.js +5 -0
  27. package/dist/components/Field/utils.js.map +1 -1
  28. package/dist/components/InlineAlert/SvgWarningTwoTone.d.ts +6 -0
  29. package/dist/components/InlineAlert/SvgWarningTwoTone.js +6 -0
  30. package/dist/components/InlineAlert/SvgWarningTwoTone.js.map +1 -1
  31. package/dist/components/InputNumber/IncrementButton.d.ts +13 -3
  32. package/dist/components/InputNumber/IncrementButton.js +11 -4
  33. package/dist/components/InputNumber/IncrementButton.js.map +1 -1
  34. package/dist/components/InputNumber/InputNumber.js +26 -10
  35. package/dist/components/InputNumber/InputNumber.js.map +1 -1
  36. package/dist/components/InputNumber/InputNumberExample.js +1 -0
  37. package/dist/components/InputNumber/InputNumberExample.js.map +1 -1
  38. package/dist/components/InputNumber/input-number.css +6 -0
  39. package/dist/components/InputNumber/input-number.css.js +6 -0
  40. package/dist/components/Link/Link.d.ts +1 -1
  41. package/dist/components/Link/Link.js +1 -1
  42. package/dist/components/OTPInput/OTPInput.d.ts +13 -3
  43. package/dist/components/OTPInput/OTPInput.js +11 -39
  44. package/dist/components/OTPInput/OTPInput.js.map +1 -1
  45. package/dist/components/OTPInput/OTPInputExample.js +6 -1
  46. package/dist/components/OTPInput/OTPInputExample.js.map +1 -1
  47. package/dist/components/OTPInput/otp-input.css +18 -17
  48. package/dist/components/OTPInput/otp-input.css.js +18 -17
  49. package/dist/components/Pagination/PageList.d.ts +6 -0
  50. package/dist/components/Pagination/PageList.js +6 -0
  51. package/dist/components/Pagination/PageList.js.map +1 -1
  52. package/dist/components/Scrim/Scrim.d.ts +0 -1
  53. package/dist/components/Scrim/Scrim.js +0 -1
  54. package/dist/components/Scrim/Scrim.js.map +1 -1
  55. package/dist/components/Select/Select.d.ts +11 -11
  56. package/dist/components/Select/Select.js +11 -11
  57. package/dist/components/Skeleton/Circular.d.ts +6 -0
  58. package/dist/components/Skeleton/Circular.js +6 -0
  59. package/dist/components/Skeleton/Circular.js.map +1 -1
  60. package/dist/components/Skeleton/Photo.d.ts +6 -0
  61. package/dist/components/Skeleton/Photo.js +6 -0
  62. package/dist/components/Skeleton/Photo.js.map +1 -1
  63. package/dist/components/Skeleton/Profile.d.ts +6 -0
  64. package/dist/components/Skeleton/Profile.js +6 -0
  65. package/dist/components/Skeleton/Profile.js.map +1 -1
  66. package/dist/components/Skeleton/Rectangular.d.ts +6 -0
  67. package/dist/components/Skeleton/Rectangular.js +6 -0
  68. package/dist/components/Skeleton/Rectangular.js.map +1 -1
  69. package/dist/components/Skeleton/Thumbnail.d.ts +6 -0
  70. package/dist/components/Skeleton/Thumbnail.js +6 -0
  71. package/dist/components/Skeleton/Thumbnail.js.map +1 -1
  72. package/dist/components/Slider/SliderIntervalDots.d.ts +6 -0
  73. package/dist/components/Slider/SliderIntervalDots.js +6 -0
  74. package/dist/components/Slider/SliderIntervalDots.js.map +1 -1
  75. package/dist/components/Snackbar/Manager.d.ts +0 -1
  76. package/dist/components/Snackbar/Manager.js +0 -1
  77. package/dist/components/Snackbar/Manager.js.map +1 -1
  78. package/dist/components/Snackbar/Snackbar.d.ts +0 -1
  79. package/dist/components/Snackbar/Snackbar.js +0 -1
  80. package/dist/components/Snackbar/Snackbar.js.map +1 -1
  81. package/dist/components/TabList/TabList.js +1 -2
  82. package/dist/components/TabList/TabList.js.map +1 -1
  83. package/dist/components/Table/Footer.d.ts +6 -0
  84. package/dist/components/Table/Footer.js +6 -0
  85. package/dist/components/Table/Footer.js.map +1 -1
  86. package/dist/components/TimePicker/Listbox.d.ts +6 -0
  87. package/dist/components/TimePicker/Listbox.js +6 -0
  88. package/dist/components/TimePicker/Listbox.js.map +1 -1
  89. package/dist/components/TimePicker/Segment.d.ts +6 -0
  90. package/dist/components/TimePicker/Segment.js +6 -0
  91. package/dist/components/TimePicker/Segment.js.map +1 -1
  92. package/dist/components/Truncated/Truncated.d.ts +0 -1
  93. package/dist/components/Truncated/Truncated.js +1 -2
  94. package/dist/components/Truncated/Truncated.js.map +1 -1
  95. package/dist/components/UIProvider/UIProvider.d.ts +0 -1
  96. package/dist/components/UIProvider/UIProvider.js +0 -1
  97. package/dist/components/UIProvider/UIProvider.js.map +1 -1
  98. package/dist/hooks/useLongPress.d.ts +30 -15
  99. package/dist/hooks/useLongPress.js +26 -42
  100. package/dist/hooks/useLongPress.js.map +1 -1
  101. package/dist/styles/base.css +9 -0
  102. package/dist/styles/base.css.js +9 -0
  103. package/package.json +1 -1
  104. package/src/components/BannerAlert/BannerAlert.tsx +5 -5
  105. package/src/components/Breadcrumb/BreadcrumbDropdown.tsx +6 -0
  106. package/src/components/CheckboxGroup/CheckboxGroupExample.tsx +1 -0
  107. package/src/components/ChipGroup/ChipGroup.rtl.test.tsx +16 -11
  108. package/src/components/ChipGroup/ChipGroup.tsx +18 -36
  109. package/src/components/ChipGroup/ChipGroupExample.tsx +64 -36
  110. package/src/components/ChipGroup/chip-group.scss +5 -3
  111. package/src/components/Drawer/Drawer.tsx +0 -1
  112. package/src/components/Field/FieldDescription.tsx +7 -5
  113. package/src/components/Field/FieldError.tsx +6 -0
  114. package/src/components/Field/FieldLabel.tsx +6 -0
  115. package/src/components/Field/utils.ts +5 -0
  116. package/src/components/InlineAlert/SvgWarningTwoTone.tsx +6 -0
  117. package/src/components/InputNumber/IncrementButton.tsx +21 -11
  118. package/src/components/InputNumber/InputNumber.tsx +33 -31
  119. package/src/components/InputNumber/InputNumberExample.tsx +1 -0
  120. package/src/components/InputNumber/input-number.scss +10 -0
  121. package/src/components/Link/Link.tsx +1 -1
  122. package/src/components/OTPInput/OTPInput.rtl.test.tsx +4 -2
  123. package/src/components/OTPInput/OTPInput.tsx +34 -63
  124. package/src/components/OTPInput/OTPInputExample.tsx +6 -1
  125. package/src/components/OTPInput/otp-input.scss +50 -45
  126. package/src/components/Pagination/PageList.tsx +6 -0
  127. package/src/components/Scrim/Scrim.tsx +0 -1
  128. package/src/components/Select/Select.tsx +11 -11
  129. package/src/components/Skeleton/Circular.tsx +6 -0
  130. package/src/components/Skeleton/Photo.tsx +6 -0
  131. package/src/components/Skeleton/Profile.tsx +6 -0
  132. package/src/components/Skeleton/Rectangular.tsx +6 -0
  133. package/src/components/Skeleton/Thumbnail.tsx +6 -0
  134. package/src/components/Slider/SliderIntervalDots.tsx +6 -0
  135. package/src/components/Snackbar/Manager.tsx +0 -1
  136. package/src/components/Snackbar/Snackbar.tsx +0 -1
  137. package/src/components/TabList/TabList.tsx +1 -2
  138. package/src/components/Table/Footer.tsx +6 -0
  139. package/src/components/TimePicker/Listbox.tsx +6 -0
  140. package/src/components/TimePicker/Segment.tsx +6 -0
  141. package/src/components/Truncated/Truncated.tsx +1 -2
  142. package/src/components/UIProvider/UIProvider.tsx +0 -1
  143. package/src/hooks/useLongPress.ts +58 -48
  144. package/src/styles/base.scss +9 -0
  145. package/dist/components/Truncated/truncated.css +0 -8
  146. package/dist/components/Truncated/truncated.css.js +0 -13
  147. package/src/components/Truncated/truncated.scss +0 -8
@@ -1,9 +1,8 @@
1
1
  import './otp-input.scss';
2
- import { useRef } from 'react';
3
2
  import { useId } from '-/hooks/useId';
4
3
  import { CommonProps } from '-/types/common';
5
4
 
6
- export type OTPInputProps = CommonProps<'id' | 'invalid' | 'name' | 'size'> & {
5
+ export type OTPInputProps = CommonProps<'aria-label' | 'id' | 'invalid' | 'name' | 'size'> & {
7
6
  /**
8
7
  * The value of the otp-input.
9
8
  *
@@ -26,6 +25,12 @@ export type OTPInputProps = CommonProps<'id' | 'invalid' | 'name' | 'size'> & {
26
25
  * @maximum 10
27
26
  */
28
27
  length?: number;
28
+ /**
29
+ * The mode of the otp-input.
30
+ *
31
+ * @default false
32
+ */
33
+ alphanumeric?: boolean;
29
34
  };
30
35
 
31
36
  /**
@@ -34,7 +39,11 @@ export type OTPInputProps = CommonProps<'id' | 'invalid' | 'name' | 'size'> & {
34
39
  * @example
35
40
  * import { OTPInput } from '@bspk/ui/OTPInput';
36
41
  *
37
- * <OTPInput name="2-auth-otp" length={4} value={otpValue} onChange={setOtpValue} />;
42
+ * () => {
43
+ * const [otpValue, setOtpValue] = useState('');
44
+ *
45
+ * return <OTPInput name="2-auth" length={6} value={otpValue} onChange={setOtpValue} alphanumeric={false} />;
46
+ * };
38
47
  *
39
48
  * @name OTPInput
40
49
  * @phase UXReview
@@ -47,70 +56,32 @@ export function OTPInput({
47
56
  length = 6,
48
57
  size = 'medium',
49
58
  invalid = false,
59
+ alphanumeric = false,
60
+ 'aria-label': ariaLabel = 'OTP input',
50
61
  }: OTPInputProps) {
51
62
  const id = useId(idProp);
52
- const value = valueProp?.slice(0, length) || '';
53
- const parentRef = useRef<HTMLDivElement | null>(null);
54
-
55
- const element = (index: number) => parentRef.current?.children[index + 1] as HTMLElement;
56
-
57
- const setIndex = (index: number, character: string) => {
58
- const charArray = value.split('');
59
- charArray[index] = character;
60
- return charArray.join('');
61
- };
62
-
63
- const updateValue = (next: string) => onChange(next.slice(0, length).toUpperCase());
63
+ const value = valueProp || '';
64
+ const activeIndex = Math.min(value.length, length - 1);
64
65
 
65
66
  return (
66
- <div
67
- data-bspk="otp-input"
68
- data-invalid={invalid || undefined}
69
- data-size={size || 'medium'}
70
- id={id}
71
- ref={parentRef}
72
- >
73
- <input name={name} type="hidden" value={value} />
74
- {Array.from({ length }, (_, index) => (
75
- <span
76
- aria-label={`OTP digit ${index + 1}`}
77
- data-digit={index + 1}
78
- key={index}
79
- onClick={(e) => {
80
- if (value[index]) return;
81
- // if a digit does not exist for the previous index then focus the previous input
82
- if (!value[index - 1]) {
83
- e.preventDefault();
84
- element(value.length)?.focus();
85
- }
86
- }}
87
- onKeyDown={(event) => {
88
- if (event.key === 'Backspace') {
89
- if (value) {
90
- // delete the last value if there is one and focus the first empty input
91
- const next = value.slice(0, -1);
92
- updateValue(next);
93
- element(next.length)?.focus();
94
- }
95
- }
96
-
97
- // if a single character key is pressed at at the current index and focus the next input
98
- if (event.key.length === 1) {
99
- updateValue(setIndex(index, event.key));
100
- element(index + 1)?.focus();
101
- }
102
- }}
103
- onPaste={(event) => {
104
- const pastedData = event.clipboardData.getData('text').trim();
105
- updateValue(pastedData);
106
- element(length - 1)?.focus();
107
- }}
108
- role="textbox"
109
- tabIndex={0}
110
- >
111
- {value?.[index] || ''}
112
- </span>
113
- ))}
67
+ <div data-bspk="otp-input" data-invalid={invalid || undefined} data-size={size || 'medium'} id={id}>
68
+ <input
69
+ aria-label={ariaLabel}
70
+ inputMode={alphanumeric ? 'text' : 'numeric'}
71
+ name={name}
72
+ onChange={(event) => {
73
+ onChange(event.target.value.trim().toUpperCase().slice(0, length));
74
+ }}
75
+ type={alphanumeric ? 'text' : 'number'}
76
+ value={value}
77
+ />
78
+ <span data-digits>
79
+ {Array.from({ length }, (_, index) => (
80
+ <span data-active={index === activeIndex || undefined} data-digit key={index}>
81
+ {value[index]}
82
+ </span>
83
+ ))}
84
+ </span>
114
85
  </div>
115
86
  );
116
87
  }
@@ -22,7 +22,12 @@ export const presets: Preset<OTPInputProps>[] = [
22
22
 
23
23
  export const OTPInputExample: ComponentExample<OTPInputProps> = {
24
24
  containerStyle: { width: '100%' },
25
- defaultState: {},
25
+ defaultState: {
26
+ value: '',
27
+ length: 6,
28
+ name: 'OTP Input',
29
+ alphanumeric: false,
30
+ },
26
31
  disableProps: [],
27
32
  presets,
28
33
  render: ({ props, Component }) => <Component {...props} />,
@@ -1,64 +1,69 @@
1
1
  [data-bspk='otp-input'] {
2
- display: flex;
3
- flex-direction: row;
4
- justify-content: center;
5
- gap: var(--spacing-sizing-02);
6
- font: var(--font);
7
2
  width: fit-content;
3
+ position: relative;
8
4
 
9
- [data-digit] {
5
+ input[inputMode] {
6
+ position: absolute;
7
+ inset: 0;
8
+ opacity: 0;
9
+ border: none;
10
+ caret-color: transparent;
11
+ }
12
+
13
+ [data-digits] {
10
14
  display: flex;
11
- align-items: center;
15
+ flex-direction: row;
12
16
  justify-content: center;
13
- border-radius: var(--radius-sm);
14
- border: 1px solid var(--stroke-neutral-base);
15
- flex-grow: 1;
16
- aspect-ratio: 1;
17
- width: var(--width);
17
+ gap: var(--spacing-sizing-02);
18
+ font: var(--font);
19
+ width: fit-content;
18
20
  position: relative;
19
- outline: none;
20
-
21
- &:hover:not(:focus)::after {
22
- background: var(--interaction-hover-opacity);
23
- content: '';
24
- position: absolute;
25
- inset: 0;
26
- border-radius: var(--radius-sm);
27
- }
21
+ pointer-events: none;
28
22
 
29
- &:active::after {
30
- background: var(--interaction-press-opacity);
31
- content: '';
32
- position: absolute;
33
- inset: 0;
23
+ [data-digit] {
24
+ display: flex;
25
+ flex-direction: row;
26
+ align-items: center;
27
+ justify-content: center;
34
28
  border-radius: var(--radius-sm);
29
+ border: 1px solid var(--stroke-neutral-base);
30
+ flex-grow: 1;
31
+ aspect-ratio: 1;
32
+ width: var(--width);
33
+ position: relative;
34
+ outline: none;
35
+ text-align: center;
35
36
  }
37
+ }
36
38
 
37
- &:focus:not(:active) {
38
- outline: solid 2px var(--stroke-neutral-focus);
39
-
40
- &:empty::before {
41
- // caret
42
- content: '';
43
- width: 2px;
44
- height: calc(var(--caret-height) - 8px);
45
- background: var(--stroke-neutral-high);
46
- animation: blink-caret 1s step-end infinite;
47
-
48
- @keyframes blink-caret {
49
- 0%,
50
- 100% {
51
- opacity: 0;
52
- }
39
+ input[inputMode]:focus + [data-digits] {
40
+ [data-digit] {
41
+ &[data-active] {
42
+ outline: solid 2px var(--stroke-neutral-focus);
53
43
 
54
- 50% {
55
- opacity: 1;
56
- }
44
+ &:empty::before {
45
+ // caret
46
+ content: '';
47
+ width: 2px;
48
+ height: calc(var(--caret-height) - 8px);
49
+ background: var(--stroke-neutral-high);
50
+ animation: blink-caret 1s step-end infinite;
57
51
  }
58
52
  }
59
53
  }
60
54
  }
61
55
 
56
+ @keyframes blink-caret {
57
+ 0%,
58
+ 100% {
59
+ opacity: 0;
60
+ }
61
+
62
+ 50% {
63
+ opacity: 1;
64
+ }
65
+ }
66
+
62
67
  &[data-size='small'] {
63
68
  --width: var(--spacing-sizing-08);
64
69
  --font: var(--subheader-medium);
@@ -3,6 +3,12 @@ import { Button } from '-/components/Button';
3
3
 
4
4
  export type PageListProps = Pick<PaginationProps, 'numPages' | 'onChange' | 'value'>;
5
5
 
6
+ /**
7
+ * PageList component displays a list of page buttons for pagination.
8
+ *
9
+ * @name PageList
10
+ * @parent Pagination
11
+ */
6
12
  export function PageList({ numPages, onChange, value }: PageListProps) {
7
13
  return Array.from({ length: numPages }, (_, index) => {
8
14
  const page = index + 1;
@@ -23,7 +23,6 @@ export type ScrimProps = CommonProps<'owner'> & {
23
23
  * attention to a modal or sheet.
24
24
  *
25
25
  * @name Scrim
26
- *
27
26
  * @phase Utility
28
27
  */
29
28
  export function Scrim({ visible = true, owner, ...props }: ScrimProps) {
@@ -68,16 +68,16 @@ export type SelectProps = CommonProps<'size'> &
68
68
  * @example
69
69
  * import { Select } from '@bspk/ui/Select';
70
70
  *
71
- * const OPTIONS = [
72
- * { id: '1', label: 'Option 1' },
73
- * { id: '2', label: 'Option 2' },
74
- * { id: '3', label: 'Option 3' },
75
- * { id: '4', label: 'Option 4' },
76
- * { id: '5', label: 'Option 5' },
77
- * { id: '6', label: 'Option 6' },
78
- * ];
79
- *
80
71
  * () => {
72
+ * const OPTIONS = [
73
+ * { id: '1', label: 'Option 1' },
74
+ * { id: '2', label: 'Option 2' },
75
+ * { id: '3', label: 'Option 3' },
76
+ * { id: '4', label: 'Option 4' },
77
+ * { id: '5', label: 'Option 5' },
78
+ * { id: '6', label: 'Option 6' },
79
+ * ];
80
+ *
81
81
  * const [selected, setSelected] = useState<string[]>([]);
82
82
  *
83
83
  * return (
@@ -85,7 +85,7 @@ export type SelectProps = CommonProps<'size'> &
85
85
  * // standalone select example
86
86
  * <Select
87
87
  * aria-label="Select an option"
88
- * itemCount={5}
88
+ * scrollLimit={5}
89
89
  * name="example-select"
90
90
  * onChange={setSelected}
91
91
  * options={OPTIONS}
@@ -98,7 +98,7 @@ export type SelectProps = CommonProps<'size'> &
98
98
  * <Field>
99
99
  * <FieldLabel>Select an option</FieldLabel>
100
100
  * <Select
101
- * itemCount={5}
101
+ * scrollLimit={5}
102
102
  * name="example-select"
103
103
  * onChange={setSelected}
104
104
  * options={OPTIONS}
@@ -1,5 +1,11 @@
1
1
  import { Skeleton, SkeletonProps } from './Skeleton';
2
2
 
3
+ /**
4
+ * SkeletonCircular component displays a circular skeleton loader.
5
+ *
6
+ * @name SkeletonCircular
7
+ * @parent Skeleton
8
+ */
3
9
  export function SkeletonCircular(props: Pick<SkeletonProps, 'height' | 'width'>) {
4
10
  return <Skeleton {...props} variant="circular" />;
5
11
  }
@@ -1,5 +1,11 @@
1
1
  import { Skeleton, SkeletonProps } from './Skeleton';
2
2
 
3
+ /**
4
+ * SkeletonPhoto component displays a photo skeleton loader.
5
+ *
6
+ * @name SkeletonPhoto
7
+ * @parent Skeleton
8
+ */
3
9
  export function SkeletonPhoto(props: Pick<SkeletonProps, 'height' | 'width'>) {
4
10
  return <Skeleton {...props} variant="photo" />;
5
11
  }
@@ -1,5 +1,11 @@
1
1
  import { Skeleton, SkeletonProps } from './Skeleton';
2
2
 
3
+ /**
4
+ * SkeletonProfile component displays a profile skeleton loader.
5
+ *
6
+ * @name SkeletonProfile
7
+ * @parent Skeleton
8
+ */
3
9
  export function SkeletonProfile(props: Pick<SkeletonProps, 'height' | 'width'>) {
4
10
  return <Skeleton {...props} variant="profile" />;
5
11
  }
@@ -1,5 +1,11 @@
1
1
  import { Skeleton, SkeletonProps } from './Skeleton';
2
2
 
3
+ /**
4
+ * SkeletonRectangular component displays a rectangular skeleton loader.
5
+ *
6
+ * @name SkeletonRectangular
7
+ * @parent Skeleton
8
+ */
3
9
  export function SkeletonRectangular(props: Pick<SkeletonProps, 'height' | 'width'>) {
4
10
  return <Skeleton {...props} variant="rectangular" />;
5
11
  }
@@ -1,5 +1,11 @@
1
1
  import { Skeleton, SkeletonProps } from './Skeleton';
2
2
 
3
+ /**
4
+ * SkeletonThumbnail component displays a thumbnail skeleton loader.
5
+ *
6
+ * @name SkeletonThumbnail
7
+ * @parent Skeleton
8
+ */
3
9
  export function SkeletonThumbnail(props: Pick<SkeletonProps, 'height' | 'width'>) {
4
10
  return <Skeleton {...props} variant="thumbnail" />;
5
11
  }
@@ -4,6 +4,12 @@ export type IntervalDotProps = Pick<SliderProps<SliderValue>, 'max' | 'min' | 'v
4
4
  step: number;
5
5
  };
6
6
 
7
+ /**
8
+ * SliderIntervalDots component displays interval dots along a slider track.
9
+ *
10
+ * @name SliderIntervalDots
11
+ * @parent Slider
12
+ */
7
13
  export function SliderIntervalDots({ step, max, min, value }: IntervalDotProps) {
8
14
  if (step <= 0) return null;
9
15
 
@@ -33,7 +33,6 @@ const SnackbarEvent = createCustomEvent<SendSnackbarProps | string | typeof CLEA
33
33
  * context. :)
34
34
  *
35
35
  * @name SnackbarManager
36
- *
37
36
  * @phase UXReview
38
37
  */
39
38
  export function SnackbarManager({ defaultTimeout = 5000 }: SnackbarManagerProps) {
@@ -104,7 +104,6 @@ export type SnackbarProps = CommonProps<'id'> & {
104
104
  * };
105
105
  *
106
106
  * @name Snackbar
107
- *
108
107
  * @phase UXReview
109
108
  */
110
109
  export function Snackbar({
@@ -2,7 +2,6 @@ import './tab-list.scss';
2
2
  import { Fragment, ReactNode, useMemo } from 'react';
3
3
  import { Badge, BadgeProps } from '-/components/Badge';
4
4
  import { Tooltip } from '-/components/Tooltip';
5
- import { Truncated } from '-/components/Truncated';
6
5
  import { useArrowNavigation } from '-/hooks/useArrowNavigation';
7
6
  import { useId } from '-/hooks/useId';
8
7
  import { ElementProps } from '-/types/common';
@@ -238,7 +237,7 @@ export function TabList({
238
237
  tabIndex={focusableOption.id === item.id ? 0 : -1}
239
238
  >
240
239
  {icon && <span aria-hidden="true">{icon}</span>}
241
- {!iconsOnly && <Truncated data-label>{item.label}</Truncated>}
240
+ {!iconsOnly && <span data-label>{item.label}</span>}
242
241
  {item.badge && !item.disabled && (
243
242
  <Badge count={item.badge} size={TAB_BADGE_SIZES[size]} />
244
243
  )}
@@ -1,5 +1,11 @@
1
1
  import { Pagination } from '-/components/Pagination';
2
2
 
3
+ /**
4
+ * TableFooter component displays pagination controls and information for a table.
5
+ *
6
+ * @name TableFooter
7
+ * @parent Table
8
+ */
3
9
  export function TableFooter({
4
10
  pageIndex,
5
11
  pageSize,
@@ -17,6 +17,12 @@ export type TimePickerListboxProps = {
17
17
  onTab?: (e: React.KeyboardEvent) => void;
18
18
  };
19
19
 
20
+ /**
21
+ * TimePickerListbox component displays a listbox for selecting time values.
22
+ *
23
+ * @name TimePickerListbox
24
+ * @parent TimePicker
25
+ */
20
26
  export function TimePickerListbox({ options, selectedValue, type: kind, onSelect, onTab }: TimePickerListboxProps) {
21
27
  const { activeElementId, arrowKeyCallbacks } = useArrowNavigation({
22
28
  ids: options.map((option) => option.id),
@@ -19,6 +19,12 @@ export type TimePickerSegmentProps<T extends string> = {
19
19
  setRef?: (element: HTMLElement | null) => void;
20
20
  };
21
21
 
22
+ /**
23
+ * TimePickerSegment component displays an individual segment of a time picker (hours, minutes, or meridiem).
24
+ *
25
+ * @name TimePickerSegment
26
+ * @parent TimePicker
27
+ */
22
28
  export function TimePickerSegment<T extends string>({
23
29
  disabled,
24
30
  name,
@@ -1,4 +1,3 @@
1
- import './truncated.scss';
2
1
  import { ElementType } from 'react';
3
2
  import { Tooltip, TooltipTriggerProps } from '-/components/Tooltip';
4
3
  import { useTruncatedText } from '-/hooks/useTruncatedText';
@@ -45,7 +44,7 @@ export function Truncated<As extends ElementType = ElementType>({
45
44
  const span = (triggerProps: TooltipTriggerProps) => (
46
45
  <span
47
46
  {...props}
48
- data-bspk-utility="truncated"
47
+ data-truncated
49
48
  ref={(node) => setElement(node)}
50
49
  {...triggerProps}
51
50
  role={isTruncated ? 'note' : props.role}
@@ -22,7 +22,6 @@ export type UIProviderProps = {
22
22
  * This provider should wrap the root of your application to ensure that all components have access to the UI context.
23
23
  *
24
24
  * @name UIProvider
25
- *
26
25
  * @phase Utility
27
26
  */
28
27
  export function UIProvider({ children }: UIProviderProps) {
@@ -1,57 +1,67 @@
1
- import { useRef, useState, MouseEvent } from 'react';
2
-
1
+ import { useRef } from 'react';
3
2
  import { useTimeout } from './useTimeout';
4
3
 
5
- export const MIN_INTERVAL = 250; // Minimum interval in milliseconds
6
- export const INTERVAL_DECREASE_FACTOR = 0.75; // Percent by which the interval decreases each time
7
- export const INITIAL_INTERVAL = 1000; // Initial interval in milliseconds
8
-
9
- const isTriggerElementDisabled = (element: HTMLElement | null) =>
10
- !element ||
11
- (element as HTMLButtonElement).disabled ||
12
- element.getAttribute('disabled') === 'true' ||
13
- element.getAttribute('aria-disabled') === 'true' ||
14
- element.getAttribute('data-disabled') === 'true' ||
15
- !element.isConnected ||
16
- !element.offsetParent;
4
+ export type LongPressProps = {
5
+ /** The callback to be invoked on long press. If false is returned, the repeating will stop. */
6
+ callback: () => boolean;
7
+ /**
8
+ * The initial delay (in ms) before repeating starts. Default is 750ms.
9
+ *
10
+ * @default 750
11
+ */
12
+ delay?: number;
13
+ /**
14
+ * The amount (in percent) to decrement the delay after each repeat. Default is 15%.
15
+ *
16
+ * @default 15
17
+ */
18
+ delayDecrement?: number;
19
+ /**
20
+ * The minimum delay (in ms) between repeats. Default is 100ms.
21
+ *
22
+ * @default 100
23
+ */
24
+ delayMin?: number;
25
+ };
17
26
 
18
- export function useLongPress(callback: () => void, disabled: boolean) {
27
+ /**
28
+ * A hook that provides long press functionality. The provided callback will be invoked once immediately on press, and
29
+ * then repeatedly after a delay, with the delay decreasing by a specified amount after each repeat.
30
+ */
31
+ export function useLongPress({
32
+ callback,
33
+ delay: initialDelay = 750,
34
+ delayDecrement = 15,
35
+ delayMin = 100,
36
+ }: LongPressProps) {
19
37
  const timeout = useTimeout();
20
- const intervalRef = useRef(INITIAL_INTERVAL);
21
-
22
- const [triggerElement, setTriggerElement] = useState<HTMLButtonElement | null>(null);
23
-
24
- if (disabled)
25
- return {
26
- onMouseDown: () => {},
27
- onMouseUp: () => {},
28
- setTriggerElement: () => {},
29
- };
30
-
31
- const run = () => {
32
- // If the element is not connected or disabled, clear the timeout, and prevent the callback
33
- if (isTriggerElementDisabled(triggerElement)) return;
34
- callback();
35
- // Decrease the interval for the next call, but not below MIN_INTERVAL
36
- if (intervalRef.current > MIN_INTERVAL) intervalRef.current = intervalRef.current * INTERVAL_DECREASE_FACTOR;
37
- timeout.set(run, intervalRef.current);
38
+ const isPressing = useRef(false);
39
+ const delay = useRef(initialDelay);
40
+
41
+ const setPressing = (pressing: boolean) => {
42
+ isPressing.current = pressing;
43
+
44
+ if (pressing) {
45
+ if (!callback()) return setPressing(false);
46
+
47
+ timeout.set(() => {
48
+ if (!isPressing.current) return setPressing(false);
49
+
50
+ const decrementMs = (delay.current * delayDecrement) / 100;
51
+ delay.current = delay.current <= delayMin ? delayMin : delay.current - decrementMs;
52
+
53
+ setPressing(true);
54
+ }, delay.current);
55
+ return;
56
+ }
57
+
58
+ delay.current = initialDelay;
59
+ timeout.clear();
38
60
  };
39
61
 
40
62
  return {
41
- onMouseDown: (event: MouseEvent) => {
42
- event.preventDefault();
43
- intervalRef.current = INITIAL_INTERVAL;
44
- callback();
45
- timeout.set(run, intervalRef.current);
46
- },
47
- onMouseUp: () => {
48
- timeout.clear();
49
- },
50
- onMouseLeave: () => {
51
- timeout.clear();
52
- },
53
- setTriggerElement,
63
+ onPointerDown: () => setPressing(true),
64
+ onPointerLeave: () => setPressing(false),
65
+ onPointerUp: () => setPressing(false),
54
66
  };
55
67
  }
56
-
57
- /** Copyright 2025 Anywhere Real Estate - CC BY 4.0 */
@@ -179,6 +179,15 @@ body[data-bspk] {
179
179
  left: auto;
180
180
  }
181
181
 
182
+ [data-truncated] {
183
+ max-width: 100%;
184
+ display: inline-block;
185
+ overflow: hidden;
186
+ text-overflow: ellipsis;
187
+ white-space: nowrap;
188
+ background-color: transparent;
189
+ }
190
+
182
191
  [data-sr-only] {
183
192
  height: 1px;
184
193
  overflow: hidden;
@@ -1,8 +0,0 @@
1
- [data-bspk-utility=truncated] {
2
- max-width: 100%;
3
- display: inline-block;
4
- overflow: hidden;
5
- text-overflow: ellipsis;
6
- white-space: nowrap;
7
- background-color: transparent;
8
- }
@@ -1,13 +0,0 @@
1
- /** * This file is generated by the build script.
2
- * Do not edit this file directly. */
3
- const style = document.createElement('style');
4
- style.appendChild(document.createTextNode(`[data-bspk-utility=truncated] {
5
- max-width: 100%;
6
- display: inline-block;
7
- overflow: hidden;
8
- text-overflow: ellipsis;
9
- white-space: nowrap;
10
- background-color: transparent;
11
- }
12
- `));
13
- document.head.appendChild(style);