@jetbrains/ring-ui 8.0.0-beta.3 → 8.0.0-beta.4

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 (60) hide show
  1. package/components/checkbox/checkbox.d.ts +1 -1
  2. package/components/collapsible-group/collapsible-group.css +138 -0
  3. package/components/collapsible-group/collapsible-group.d.ts +20 -0
  4. package/components/collapsible-group/collapsible-group.js +73 -0
  5. package/components/editable-heading/editable-heading.js +14 -14
  6. package/components/expand/collapsible-group.css +23 -89
  7. package/components/expand/collapsible-group.d.ts +8 -20
  8. package/components/expand/collapsible-group.js +14 -73
  9. package/components/global/compose-refs.d.ts +2 -1
  10. package/components/global/compose-refs.js +24 -8
  11. package/components/global/focus-with-temporary-tabindex.d.ts +11 -0
  12. package/components/global/focus-with-temporary-tabindex.js +21 -0
  13. package/components/global/intersection-observer-context.d.ts +29 -9
  14. package/components/global/intersection-observer-context.js +43 -24
  15. package/components/global/is-within-interactive-element.d.ts +6 -0
  16. package/components/global/is-within-interactive-element.js +26 -0
  17. package/components/global/is-within-navigable-element.d.ts +6 -0
  18. package/components/global/is-within-navigable-element.js +27 -0
  19. package/components/global/parse-css-duration.d.ts +5 -0
  20. package/components/global/parse-css-duration.js +13 -0
  21. package/components/global/schedule-with-cleanup.d.ts +12 -0
  22. package/components/global/schedule-with-cleanup.js +34 -0
  23. package/components/global/table-selection.js +1 -1
  24. package/components/input/input.d.ts +1 -1
  25. package/components/legacy-table/row.d.ts +1 -1
  26. package/components/popup/popup.d.ts +2 -0
  27. package/components/popup/popup.js +2 -2
  28. package/components/select/select-popup.d.ts +1 -1
  29. package/components/select/select.d.ts +1 -1
  30. package/components/table/default-item-renderer.d.ts +23 -10
  31. package/components/table/default-item-renderer.js +40 -19
  32. package/components/table/internal/column-animation.d.ts +16 -0
  33. package/components/table/internal/column-animation.js +45 -0
  34. package/components/table/internal/table-header.d.ts +4 -0
  35. package/components/table/internal/table-header.js +357 -0
  36. package/components/table/internal/virtual-items.d.ts +34 -0
  37. package/components/table/{table-virtualize.js → internal/virtual-items.js} +68 -33
  38. package/components/table/item-virtualization.d.ts +35 -0
  39. package/components/table/item-virtualization.js +28 -0
  40. package/components/table/table-const.d.ts +40 -6
  41. package/components/table/table-const.js +14 -5
  42. package/components/table/table-primitives.d.ts +9 -16
  43. package/components/table/table-primitives.js +6 -60
  44. package/components/table/table-props.d.ts +280 -0
  45. package/components/table/table-props.js +1 -0
  46. package/components/table/table.css +171 -66
  47. package/components/table/table.d.ts +205 -209
  48. package/components/table/table.js +298 -2
  49. package/components/util-stories.d.ts +26 -0
  50. package/components/util-stories.js +61 -0
  51. package/package.json +29 -30
  52. package/components/date-picker/use-intersection-observer.d.ts +0 -6
  53. package/components/date-picker/use-intersection-observer.js +0 -48
  54. package/components/global/composeRefs.d.ts +0 -6
  55. package/components/global/composeRefs.js +0 -7
  56. package/components/table/table-component.d.ts +0 -80
  57. package/components/table/table-component.js +0 -130
  58. package/components/table/table-row-focus.d.ts +0 -4
  59. package/components/table/table-row-focus.js +0 -42
  60. package/components/table/table-virtualize.d.ts +0 -32
@@ -24,6 +24,6 @@ export default class Checkbox extends PureComponent<CheckboxProps> {
24
24
  componentDidUpdate(prevProps: CheckboxProps): void;
25
25
  input?: HTMLInputElement | null;
26
26
  inputRef: (el: HTMLInputElement | null) => void;
27
- composedInputRef: import("memoize-one").MemoizedFn<(...refs: (Ref<HTMLInputElement> | undefined)[]) => (value: T | null) => void>;
27
+ composedInputRef: import("memoize-one").MemoizedFn<(...refs: (Ref<HTMLInputElement> | undefined)[]) => (value: T | null) => () => void>;
28
28
  render(): import("react").JSX.Element;
29
29
  }
@@ -0,0 +1,138 @@
1
+ @import '../global/variables.css';
2
+
3
+ .expand .collapseRoot {
4
+ display: flex;
5
+ flex-direction: column;
6
+ }
7
+
8
+ .expand {
9
+ display: flex;
10
+ flex-direction: column;
11
+
12
+ box-sizing: border-box;
13
+
14
+ border: 1px solid var(--ring-line-color);
15
+ border-radius: var(--ring-border-radius-large);
16
+ outline: none;
17
+ background: var(--ring-content-background-color);
18
+ }
19
+
20
+ .hovered {
21
+ box-shadow: var(--ring-popup-shadow);
22
+ }
23
+
24
+ .expanded {
25
+ box-shadow: var(--ring-popup-shadow);
26
+ }
27
+
28
+ .focused {
29
+ box-shadow: 0 0 0 2px var(--ring-border-hover-color), var(--ring-popup-shadow);
30
+ }
31
+
32
+ .header {
33
+ display: flex;
34
+ align-items: center;
35
+ gap: calc(var(--ring-unit) * 1.5);
36
+
37
+ height: 28px;
38
+ }
39
+
40
+ .headerButton {
41
+ display: block;
42
+
43
+ box-sizing: border-box;
44
+ width: 100%;
45
+ padding: calc(var(--ring-unit) * 1.5);
46
+
47
+ cursor: pointer;
48
+ text-align: left;
49
+
50
+ color: inherit;
51
+
52
+ border: 0;
53
+ background: transparent;
54
+
55
+ font: inherit;
56
+ }
57
+
58
+ .headerButton:focus-visible {
59
+ outline: none;
60
+ }
61
+
62
+ .headerStatic {
63
+ display: block;
64
+
65
+ padding: calc(var(--ring-unit) * 1.5);
66
+ }
67
+
68
+ .headerContent {
69
+ display: flex;
70
+ align-items: center;
71
+ gap: var(--ring-unit);
72
+
73
+ flex: 1;
74
+
75
+ min-width: 0;
76
+ }
77
+
78
+ .avatarGroup {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: var(--ring-unit);
82
+
83
+ flex-shrink: 0;
84
+ }
85
+
86
+ .title {
87
+ color: var(--ring-text-color);
88
+
89
+ font-size: var(--ring-font-size);
90
+ font-weight: var(--ring-font-weight-bold);
91
+ line-height: var(--ring-line-height);
92
+ }
93
+
94
+ .subtitleGroup {
95
+ display: flex;
96
+ align-items: center;
97
+ gap: var(--ring-unit);
98
+
99
+ min-width: 0;
100
+ }
101
+
102
+ .subtitle {
103
+ color: var(--ring-secondary-color);
104
+
105
+ font-size: var(--ring-font-size);
106
+ line-height: var(--ring-line-height);
107
+ }
108
+
109
+ .subtitleChevron {
110
+ color: var(--ring-secondary-color);
111
+ }
112
+
113
+ .toggle {
114
+ display: flex;
115
+ align-items: center;
116
+
117
+ flex-shrink: 0;
118
+ justify-content: center;
119
+
120
+ width: calc(var(--ring-unit) * 3);
121
+ height: calc(var(--ring-unit) * 3);
122
+
123
+ border-radius: calc(var(--ring-unit) / 2);
124
+ background: var(--ring-secondary-background-color);
125
+ }
126
+
127
+ .toggleIcon {
128
+ color: var(--ring-secondary-color);
129
+ }
130
+
131
+ .body {
132
+ padding: 0 calc(var(--ring-unit) * 2) calc(var(--ring-unit) * 1.5);
133
+
134
+ color: var(--ring-text-color);
135
+
136
+ font-size: var(--ring-font-size);
137
+ line-height: var(--ring-line-height);
138
+ }
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ export interface CollapsibleGroupProps {
3
+ ref?: React.Ref<HTMLDivElement>;
4
+ avatar?: React.ReactNode;
5
+ title: React.ReactNode;
6
+ subtitle?: React.ReactNode;
7
+ children?: React.ReactNode;
8
+ className?: string | null | undefined;
9
+ defaultExpanded?: boolean;
10
+ expanded?: boolean | null | undefined;
11
+ onChange?: (expanded: boolean) => void;
12
+ disableAnimation?: boolean;
13
+ interactive?: boolean;
14
+ 'data-test'?: string | null | undefined;
15
+ }
16
+ declare const CollapsibleGroup: {
17
+ ({ ref, avatar, title, subtitle, children, className, defaultExpanded, expanded, onChange, disableAnimation, interactive, "data-test": dataTest, }: CollapsibleGroupProps): React.JSX.Element;
18
+ displayName: string;
19
+ };
20
+ export default CollapsibleGroup;
@@ -0,0 +1,73 @@
1
+ import React, { use, useState } from 'react';
2
+ import classNames from 'classnames';
3
+ import chevronRightIcon from '@jetbrains/icons/chevron-12px-right';
4
+ import chevronDownIcon from '@jetbrains/icons/chevron-12px-down';
5
+ import Icon from '../icon/icon';
6
+ import Collapse from '../collapse/collapse';
7
+ import CollapseContent from '../collapse/collapse-content';
8
+ import { CollapseContext } from '../collapse/collapse-context';
9
+ import styles from './collapsible-group.css';
10
+ function CollapsibleGroupHeaderContent({ avatar, titleContent, subtitle }) {
11
+ const { collapsed } = use(CollapseContext);
12
+ return (<span className={styles.header}>
13
+ <span className={styles.headerContent}>
14
+ <span className={styles.avatarGroup}>
15
+ {avatar}
16
+ <span className={styles.title}>{titleContent}</span>
17
+ </span>
18
+ {subtitle ? (<span className={styles.subtitleGroup}>
19
+ <Icon className={styles.subtitleChevron} glyph={chevronRightIcon} aria-hidden/>
20
+ <span className={styles.subtitle}>{subtitle}</span>
21
+ </span>) : null}
22
+ </span>
23
+ <span className={styles.toggle} aria-hidden>
24
+ <Icon className={styles.toggleIcon} glyph={collapsed ? chevronRightIcon : chevronDownIcon}/>
25
+ </span>
26
+ </span>);
27
+ }
28
+ function CollapsibleGroupHeader({ avatar, titleContent, subtitle, ...buttonProps }) {
29
+ const { setCollapsed, collapsed, id } = use(CollapseContext);
30
+ return (<button type='button' {...buttonProps} className={styles.headerButton} onClick={setCollapsed} aria-controls={`collapse-content-${id}`} aria-expanded={!collapsed}>
31
+ <CollapsibleGroupHeaderContent avatar={avatar} titleContent={titleContent} subtitle={subtitle}/>
32
+ </button>);
33
+ }
34
+ function CollapsibleGroupHeaderStatic({ avatar, titleContent, subtitle }) {
35
+ return (<span className={styles.headerStatic}>
36
+ <CollapsibleGroupHeaderContent avatar={avatar} titleContent={titleContent} subtitle={subtitle}/>
37
+ </span>);
38
+ }
39
+ const CollapsibleGroup = ({ ref, avatar, title, subtitle, children, className, defaultExpanded = false, expanded = null, onChange = () => { }, disableAnimation = false, interactive = true, 'data-test': dataTest, }) => {
40
+ const [innerExpanded, setInnerExpanded] = useState(defaultExpanded);
41
+ const [hovered, setHovered] = useState(false);
42
+ const [focused, setFocused] = useState(false);
43
+ const isExpanded = expanded ?? innerExpanded;
44
+ const handleChange = (collapsed) => {
45
+ const nextExpanded = !collapsed;
46
+ if (expanded == null) {
47
+ setInnerExpanded(nextExpanded);
48
+ }
49
+ onChange(nextExpanded);
50
+ };
51
+ const onBlur = (event) => {
52
+ const nextTarget = event.relatedTarget;
53
+ if (nextTarget instanceof Node && event.currentTarget.contains(nextTarget)) {
54
+ return;
55
+ }
56
+ setFocused(false);
57
+ };
58
+ const classes = classNames(styles.expand, className, {
59
+ [styles.hovered]: hovered,
60
+ [styles.expanded]: isExpanded,
61
+ [styles.focused]: focused,
62
+ });
63
+ return (<div ref={ref} className={classes} data-test={dataTest}>
64
+ <Collapse defaultCollapsed={!defaultExpanded} collapsed={expanded == null ? null : !expanded} onChange={handleChange} disableAnimation={disableAnimation} className={styles.collapseRoot}>
65
+ {interactive ? (<CollapsibleGroupHeader avatar={avatar} titleContent={title} subtitle={subtitle} onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} onFocus={() => setFocused(true)} onBlur={onBlur}/>) : (<CollapsibleGroupHeaderStatic avatar={avatar} titleContent={title} subtitle={subtitle}/>)}
66
+ <CollapseContent>
67
+ <div className={styles.body}>{children}</div>
68
+ </CollapseContent>
69
+ </Collapse>
70
+ </div>);
71
+ };
72
+ CollapsibleGroup.displayName = 'CollapsibleGroup';
73
+ export default CollapsibleGroup;
@@ -1,4 +1,4 @@
1
- import { useEffect, useEffectEvent } from 'react';
1
+ import { useEffect, useEffectEvent, useLayoutEffect } from 'react';
2
2
  import * as React from 'react';
3
3
  import classNames from 'classnames';
4
4
  import Heading, { Levels } from '../heading/heading';
@@ -45,26 +45,20 @@ export const EditableHeading = (props) => {
45
45
  });
46
46
  const headingClasses = classNames(styles.heading, headingClassName, size !== Size.AUTO && styles[`size${size}`]);
47
47
  const inputClasses = classNames('ring-js-shortcuts', styles.input, styles.textarea, { [styles.textareaNotOverflow]: !isOverflow }, size !== Size.AUTO && inputStyles[`size${size}`], level !== Levels.H4 && styles[`level${level}`], inputClassName);
48
- const stretch = (el) => {
49
- if (!el || !el.style) {
50
- return;
51
- }
52
- el.style.height = '0';
53
- const { paddingTop, paddingBottom } = window.getComputedStyle(el);
54
- el.style.height = `${el.scrollHeight - parseFloat(paddingTop) - parseFloat(paddingBottom)}px`;
55
- };
56
- const checkValue = (el) => {
48
+ const checkValue = React.useCallback((el) => {
57
49
  if (multiline && el && el.scrollHeight >= el.clientHeight) {
58
- stretch(el);
50
+ el.style.height = '0';
51
+ const { paddingTop, paddingBottom } = window.getComputedStyle(el);
52
+ el.style.height = `${el.scrollHeight - parseFloat(paddingTop) - parseFloat(paddingBottom)}px`;
59
53
  }
60
- };
61
- const checkOverflow = (el) => {
54
+ }, [multiline]);
55
+ const checkOverflow = React.useCallback((el) => {
62
56
  const scrollHeight = el.scrollHeight || 0;
63
57
  const clientHeight = el.clientHeight || 0;
64
58
  const scrollTop = el.scrollTop || 0;
65
59
  setIsScrolledToBottom(scrollHeight - clientHeight <= scrollTop);
66
60
  setIsOverflow(scrollHeight > clientHeight);
67
- };
61
+ }, []);
68
62
  const onHeadingMouseDown = () => {
69
63
  setIsMouseDown(true);
70
64
  };
@@ -100,6 +94,12 @@ export const EditableHeading = (props) => {
100
94
  setIsInFocus(false);
101
95
  onBlur?.(e);
102
96
  };
97
+ useLayoutEffect(() => {
98
+ if (multiline && isEditing && textAreaRef.current) {
99
+ checkValue(textAreaRef.current);
100
+ checkOverflow(textAreaRef.current);
101
+ }
102
+ }, [checkOverflow, checkValue, isEditing, multiline]);
103
103
  useEffect(() => {
104
104
  window.addEventListener('mousemove', onMouseMove);
105
105
  window.addEventListener('mouseup', onMouseUp);
@@ -1,138 +1,72 @@
1
- @import '../global/variables.css';
2
-
3
- .expand .collapseRoot {
4
- display: flex;
5
- flex-direction: column;
6
- }
1
+ /* Deprecated alias of `../collapsible-group/collapsible-group.css` (removed in Ring UI 8.0).
2
+ Class names are forwarded via `composes` so the canonical stylesheet stays the single source of
3
+ the actual rules, while `import styles from '.../components/expand/collapsible-group.css'` keeps
4
+ returning the full token map. */
7
5
 
8
6
  .expand {
9
- display: flex;
10
- flex-direction: column;
11
-
12
- box-sizing: border-box;
7
+ composes: expand from '../collapsible-group/collapsible-group.css';
8
+ }
13
9
 
14
- border: 1px solid var(--ring-line-color);
15
- border-radius: var(--ring-border-radius-large);
16
- outline: none;
17
- background: var(--ring-content-background-color);
10
+ .collapseRoot {
11
+ composes: collapseRoot from '../collapsible-group/collapsible-group.css';
18
12
  }
19
13
 
20
14
  .hovered {
21
- box-shadow: var(--ring-popup-shadow);
15
+ composes: hovered from '../collapsible-group/collapsible-group.css';
22
16
  }
23
17
 
24
18
  .expanded {
25
- box-shadow: var(--ring-popup-shadow);
19
+ composes: expanded from '../collapsible-group/collapsible-group.css';
26
20
  }
27
21
 
28
22
  .focused {
29
- box-shadow: 0 0 0 2px var(--ring-border-hover-color), var(--ring-popup-shadow);
23
+ composes: focused from '../collapsible-group/collapsible-group.css';
30
24
  }
31
25
 
32
26
  .header {
33
- display: flex;
34
- align-items: center;
35
- gap: calc(var(--ring-unit) * 1.5);
36
-
37
- height: 28px;
27
+ composes: header from '../collapsible-group/collapsible-group.css';
38
28
  }
39
29
 
40
30
  .headerButton {
41
- display: block;
42
-
43
- box-sizing: border-box;
44
- width: 100%;
45
- padding: calc(var(--ring-unit) * 1.5);
46
-
47
- cursor: pointer;
48
- text-align: left;
49
-
50
- color: inherit;
51
-
52
- border: 0;
53
- background: transparent;
54
-
55
- font: inherit;
56
- }
57
-
58
- .headerButton:focus-visible {
59
- outline: none;
31
+ composes: headerButton from '../collapsible-group/collapsible-group.css';
60
32
  }
61
33
 
62
34
  .headerStatic {
63
- display: block;
64
-
65
- padding: calc(var(--ring-unit) * 1.5);
35
+ composes: headerStatic from '../collapsible-group/collapsible-group.css';
66
36
  }
67
37
 
68
38
  .headerContent {
69
- display: flex;
70
- align-items: center;
71
- gap: var(--ring-unit);
72
-
73
- flex: 1;
74
-
75
- min-width: 0;
39
+ composes: headerContent from '../collapsible-group/collapsible-group.css';
76
40
  }
77
41
 
78
42
  .avatarGroup {
79
- display: flex;
80
- align-items: center;
81
- gap: var(--ring-unit);
82
-
83
- flex-shrink: 0;
43
+ composes: avatarGroup from '../collapsible-group/collapsible-group.css';
84
44
  }
85
45
 
86
46
  .title {
87
- color: var(--ring-text-color);
88
-
89
- font-size: var(--ring-font-size);
90
- font-weight: var(--ring-font-weight-bold);
91
- line-height: var(--ring-line-height);
47
+ composes: title from '../collapsible-group/collapsible-group.css';
92
48
  }
93
49
 
94
50
  .subtitleGroup {
95
- display: flex;
96
- align-items: center;
97
- gap: var(--ring-unit);
98
-
99
- min-width: 0;
51
+ composes: subtitleGroup from '../collapsible-group/collapsible-group.css';
100
52
  }
101
53
 
102
54
  .subtitle {
103
- color: var(--ring-secondary-color);
104
-
105
- font-size: var(--ring-font-size);
106
- line-height: var(--ring-line-height);
55
+ composes: subtitle from '../collapsible-group/collapsible-group.css';
107
56
  }
108
57
 
109
58
  .subtitleChevron {
110
- color: var(--ring-secondary-color);
59
+ composes: subtitleChevron from '../collapsible-group/collapsible-group.css';
111
60
  }
112
61
 
113
62
  .toggle {
114
- display: flex;
115
- align-items: center;
116
-
117
- flex-shrink: 0;
118
- justify-content: center;
119
-
120
- width: calc(var(--ring-unit) * 3);
121
- height: calc(var(--ring-unit) * 3);
122
-
123
- border-radius: calc(var(--ring-unit) / 2);
124
- background: var(--ring-secondary-background-color);
63
+ composes: toggle from '../collapsible-group/collapsible-group.css';
125
64
  }
126
65
 
127
66
  .toggleIcon {
128
- color: var(--ring-secondary-color);
67
+ composes: toggleIcon from '../collapsible-group/collapsible-group.css';
129
68
  }
130
69
 
131
70
  .body {
132
- padding: 0 calc(var(--ring-unit) * 2) calc(var(--ring-unit) * 1.5);
133
-
134
- color: var(--ring-text-color);
135
-
136
- font-size: var(--ring-font-size);
137
- line-height: var(--ring-line-height);
71
+ composes: body from '../collapsible-group/collapsible-group.css';
138
72
  }
@@ -1,20 +1,8 @@
1
- import React from 'react';
2
- export interface CollapsibleGroupProps {
3
- ref?: React.Ref<HTMLDivElement>;
4
- avatar?: React.ReactNode;
5
- title: React.ReactNode;
6
- subtitle?: React.ReactNode;
7
- children?: React.ReactNode;
8
- className?: string | null | undefined;
9
- defaultExpanded?: boolean;
10
- expanded?: boolean | null | undefined;
11
- onChange?: (expanded: boolean) => void;
12
- disableAnimation?: boolean;
13
- interactive?: boolean;
14
- 'data-test'?: string | null | undefined;
15
- }
16
- declare const CollapsibleGroup: {
17
- ({ ref, avatar, title, subtitle, children, className, defaultExpanded, expanded, onChange, disableAnimation, interactive, "data-test": dataTest, }: CollapsibleGroupProps): React.JSX.Element;
18
- displayName: string;
19
- };
20
- export default CollapsibleGroup;
1
+ import { type CollapsibleGroupProps } from '../collapsible-group/collapsible-group';
2
+ /**
3
+ * @deprecated The `expand` module has been renamed to `collapsible-group`. This re-export will be removed in
4
+ * Ring UI 8.0. Use `CollapsibleGroup` from `@jetbrains/ring-ui/components/collapsible-group/collapsible-group` instead.
5
+ */
6
+ declare const DeprecatedCollapsibleGroup: import("react").ForwardRefExoticComponent<Omit<CollapsibleGroupProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
7
+ export type { CollapsibleGroupProps };
8
+ export default DeprecatedCollapsibleGroup;
@@ -1,73 +1,14 @@
1
- import React, { use, useState } from 'react';
2
- import classNames from 'classnames';
3
- import chevronRightIcon from '@jetbrains/icons/chevron-12px-right';
4
- import chevronDownIcon from '@jetbrains/icons/chevron-12px-down';
5
- import Icon from '../icon/icon';
6
- import Collapse from '../collapse/collapse';
7
- import CollapseContent from '../collapse/collapse-content';
8
- import { CollapseContext } from '../collapse/collapse-context';
9
- import styles from './collapsible-group.css';
10
- function CollapsibleGroupHeaderContent({ avatar, titleContent, subtitle }) {
11
- const { collapsed } = use(CollapseContext);
12
- return (<span className={styles.header}>
13
- <span className={styles.headerContent}>
14
- <span className={styles.avatarGroup}>
15
- {avatar}
16
- <span className={styles.title}>{titleContent}</span>
17
- </span>
18
- {subtitle ? (<span className={styles.subtitleGroup}>
19
- <Icon className={styles.subtitleChevron} glyph={chevronRightIcon} aria-hidden/>
20
- <span className={styles.subtitle}>{subtitle}</span>
21
- </span>) : null}
22
- </span>
23
- <span className={styles.toggle} aria-hidden>
24
- <Icon className={styles.toggleIcon} glyph={collapsed ? chevronRightIcon : chevronDownIcon}/>
25
- </span>
26
- </span>);
27
- }
28
- function CollapsibleGroupHeader({ avatar, titleContent, subtitle, ...buttonProps }) {
29
- const { setCollapsed, collapsed, id } = use(CollapseContext);
30
- return (<button type='button' {...buttonProps} className={styles.headerButton} onClick={setCollapsed} aria-controls={`collapse-content-${id}`} aria-expanded={!collapsed}>
31
- <CollapsibleGroupHeaderContent avatar={avatar} titleContent={titleContent} subtitle={subtitle}/>
32
- </button>);
33
- }
34
- function CollapsibleGroupHeaderStatic({ avatar, titleContent, subtitle }) {
35
- return (<span className={styles.headerStatic}>
36
- <CollapsibleGroupHeaderContent avatar={avatar} titleContent={titleContent} subtitle={subtitle}/>
37
- </span>);
38
- }
39
- const CollapsibleGroup = ({ ref, avatar, title, subtitle, children, className, defaultExpanded = false, expanded = null, onChange = () => { }, disableAnimation = false, interactive = true, 'data-test': dataTest, }) => {
40
- const [innerExpanded, setInnerExpanded] = useState(defaultExpanded);
41
- const [hovered, setHovered] = useState(false);
42
- const [focused, setFocused] = useState(false);
43
- const isExpanded = expanded ?? innerExpanded;
44
- const handleChange = (collapsed) => {
45
- const nextExpanded = !collapsed;
46
- if (expanded == null) {
47
- setInnerExpanded(nextExpanded);
48
- }
49
- onChange(nextExpanded);
50
- };
51
- const onBlur = (event) => {
52
- const nextTarget = event.relatedTarget;
53
- if (nextTarget instanceof Node && event.currentTarget.contains(nextTarget)) {
54
- return;
55
- }
56
- setFocused(false);
57
- };
58
- const classes = classNames(styles.expand, className, {
59
- [styles.hovered]: hovered,
60
- [styles.expanded]: isExpanded,
61
- [styles.focused]: focused,
62
- });
63
- return (<div ref={ref} className={classes} data-test={dataTest}>
64
- <Collapse defaultCollapsed={!defaultExpanded} collapsed={expanded == null ? null : !expanded} onChange={handleChange} disableAnimation={disableAnimation} className={styles.collapseRoot}>
65
- {interactive ? (<CollapsibleGroupHeader avatar={avatar} titleContent={title} subtitle={subtitle} onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} onFocus={() => setFocused(true)} onBlur={onBlur}/>) : (<CollapsibleGroupHeaderStatic avatar={avatar} titleContent={title} subtitle={subtitle}/>)}
66
- <CollapseContent>
67
- <div className={styles.body}>{children}</div>
68
- </CollapseContent>
69
- </Collapse>
70
- </div>);
71
- };
72
- CollapsibleGroup.displayName = 'CollapsibleGroup';
73
- export default CollapsibleGroup;
1
+ import { forwardRef } from 'react';
2
+ import deprecate from 'util-deprecate';
3
+ import CollapsibleGroup from '../collapsible-group/collapsible-group';
4
+ const warnDeprecation = deprecate(() => { }, '`CollapsibleGroup` from `@jetbrains/ring-ui/components/expand/collapsible-group` is deprecated and will be removed in Ring UI 8.0. Import it from `@jetbrains/ring-ui/components/collapsible-group/collapsible-group` instead.');
5
+ /**
6
+ * @deprecated The `expand` module has been renamed to `collapsible-group`. This re-export will be removed in
7
+ * Ring UI 8.0. Use `CollapsibleGroup` from `@jetbrains/ring-ui/components/collapsible-group/collapsible-group` instead.
8
+ */
9
+ const DeprecatedCollapsibleGroup = forwardRef((props, ref) => {
10
+ warnDeprecation();
11
+ return <CollapsibleGroup ref={ref} {...props}/>;
12
+ });
13
+ DeprecatedCollapsibleGroup.displayName = 'CollapsibleGroup';
14
+ export default DeprecatedCollapsibleGroup;
@@ -1,2 +1,3 @@
1
1
  import { type Ref } from 'react';
2
- export declare function createComposedRef<T>(): import("memoize-one").MemoizedFn<(...refs: (Ref<T> | undefined)[]) => (value: T | null) => void>;
2
+ export declare function createComposedRef<T>(): import("memoize-one").MemoizedFn<(...refs: (Ref<T> | undefined)[]) => (value: T | null) => () => void>;
3
+ export declare function useComposedRef<T>(...refs: (Ref<T> | undefined)[]): Ref<T>;
@@ -1,14 +1,30 @@
1
+ import { useMemo } from 'react';
1
2
  import memoizeOne from 'memoize-one';
2
3
  function composeRefs(...refs) {
3
- return (value) => refs.forEach(ref => {
4
- if (typeof ref === 'function') {
5
- ref(value);
6
- }
7
- else if (ref) {
8
- ref.current = value;
9
- }
10
- });
4
+ return (value) => {
5
+ const cleanups = [];
6
+ refs.forEach(ref => {
7
+ if (typeof ref === 'function') {
8
+ const cleanup = ref(value);
9
+ if (typeof cleanup === 'function') {
10
+ cleanups.push(cleanup);
11
+ }
12
+ }
13
+ else if (ref) {
14
+ ref.current = value;
15
+ }
16
+ });
17
+ return () => cleanups.forEach(cleanup => cleanup());
18
+ };
11
19
  }
12
20
  export function createComposedRef() {
13
21
  return memoizeOne((composeRefs));
14
22
  }
23
+ export function useComposedRef(...refs) {
24
+ /**
25
+ * The React Compiler doesn't allow non-literal arrays in useMemo
26
+ * dependency lists, so we still use memoizeOne under the hood.
27
+ */
28
+ const composer = useMemo(() => createComposedRef(), []);
29
+ return composer(...refs);
30
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Focuses an element, temporarily adding `tabindex="0"` if necessary.
3
+ *
4
+ * If the element does not already have a `tabindex` attribute, one is
5
+ * added before focusing and automatically removed when the element loses
6
+ * focus.
7
+ *
8
+ * This is useful when implementing roving tabindex patterns on elements
9
+ * that are not normally focusable.
10
+ */
11
+ export declare function focusWithTemporaryTabIndex(element: HTMLElement): void;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Focuses an element, temporarily adding `tabindex="0"` if necessary.
3
+ *
4
+ * If the element does not already have a `tabindex` attribute, one is
5
+ * added before focusing and automatically removed when the element loses
6
+ * focus.
7
+ *
8
+ * This is useful when implementing roving tabindex patterns on elements
9
+ * that are not normally focusable.
10
+ */
11
+ export function focusWithTemporaryTabIndex(element) {
12
+ if (!element.hasAttribute('tabindex')) {
13
+ element.tabIndex = 0;
14
+ function onBlur() {
15
+ element.removeAttribute('tabindex');
16
+ element.removeEventListener('blur', onBlur);
17
+ }
18
+ element.addEventListener('blur', onBlur);
19
+ }
20
+ element.focus();
21
+ }