@kaizen/components 3.2.0 → 3.3.0
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.
- package/dist/cjs/src/Avatar/Avatar.cjs +18 -14
- package/dist/cjs/src/Focusable/Focusable.cjs +5 -4
- package/dist/cjs/src/MultiSelect/MultiSelect.cjs +4 -2
- package/dist/cjs/src/RichTextEditor/RichTextEditor/RichTextEditor.cjs +4 -1
- package/dist/cjs/src/RichTextEditor/RichTextEditor/utils/inputrules.cjs +0 -1
- package/dist/cjs/src/RichTextEditor/utils/core/createRichTextEditor.cjs +2 -1
- package/dist/cjs/src/RichTextEditor/utils/core/hooks/useRichTextEditor.cjs +24 -9
- package/dist/cjs/src/SingleSelect/SingleSelect.cjs +4 -2
- package/dist/esm/src/Avatar/Avatar.mjs +18 -15
- package/dist/esm/src/Focusable/Focusable.mjs +5 -4
- package/dist/esm/src/MultiSelect/MultiSelect.mjs +4 -2
- package/dist/esm/src/RichTextEditor/RichTextEditor/RichTextEditor.mjs +4 -1
- package/dist/esm/src/RichTextEditor/RichTextEditor/utils/inputrules.mjs +0 -1
- package/dist/esm/src/RichTextEditor/utils/core/createRichTextEditor.mjs +2 -1
- package/dist/esm/src/RichTextEditor/utils/core/hooks/useRichTextEditor.mjs +24 -9
- package/dist/esm/src/SingleSelect/SingleSelect.mjs +4 -2
- package/dist/styles.css +15 -15
- package/dist/types/Focusable/Focusable.d.ts +8 -3
- package/dist/types/MultiSelect/MultiSelect.d.ts +3 -2
- package/dist/types/Notification/index.d.ts +1 -0
- package/dist/types/RichTextEditor/RichTextEditor/RichTextEditor.d.ts +2 -1
- package/dist/types/RichTextEditor/utils/core/createRichTextEditor.d.ts +1 -0
- package/dist/types/RichTextEditor/utils/core/hooks/useRichTextEditor.d.ts +3 -2
- package/dist/types/SingleSelect/SingleSelect.d.ts +3 -3
- package/package.json +1 -1
- package/src/Avatar/Avatar.tsx +19 -11
- package/src/Focusable/Focusable.tsx +17 -7
- package/src/MultiSelect/MultiSelect.tsx +4 -1
- package/src/Notification/index.ts +1 -0
- package/src/RichTextEditor/RichTextEditor/RichTextEditor.tsx +3 -0
- package/src/RichTextEditor/utils/core/createRichTextEditor.spec.ts +1 -1
- package/src/RichTextEditor/utils/core/createRichTextEditor.ts +2 -0
- package/src/RichTextEditor/utils/core/hooks/useRichTextEditor.spec.tsx +8 -3
- package/src/RichTextEditor/utils/core/hooks/useRichTextEditor.ts +20 -7
- package/src/SingleSelect/SingleSelect.tsx +5 -3
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
1
|
+
import { type ElementType, type HTMLAttributes, type ReactNode } from 'react';
|
|
2
2
|
import { type FocusableOptions } from 'react-aria';
|
|
3
|
-
export type FocusableProps = {
|
|
3
|
+
export type FocusableProps<T extends ElementType = 'div'> = {
|
|
4
4
|
children: ReactNode;
|
|
5
|
+
/**
|
|
6
|
+
* The HTML element to render as. Defaults to `div` to avoid an api change
|
|
7
|
+
* but should be set to 'span' if rendering within something like a 'p' tag to avoid invalid HTML.
|
|
8
|
+
*/
|
|
9
|
+
tag?: T;
|
|
5
10
|
} & FocusableOptions & HTMLAttributes<HTMLDivElement>;
|
|
6
|
-
export declare const Focusable: ({ children, className, ...props }: FocusableProps) => JSX.Element;
|
|
11
|
+
export declare const Focusable: <T extends ElementType = "div">({ children, className, tag, ...props }: FocusableProps<T>) => JSX.Element;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { type HTMLAttributes } from 'react';
|
|
1
|
+
import React, { type HTMLAttributes } from 'react';
|
|
2
2
|
import { type FieldMessageProps } from "../FieldMessage";
|
|
3
3
|
import { type OverrideClassName } from "../types/OverrideClassName";
|
|
4
4
|
import { type MultiSelectOptionsProps } from './subcomponents/MultiSelectOptions';
|
|
5
5
|
import { type MultiSelectOption, type ValidationMessage } from './types';
|
|
6
6
|
export type MultiSelectProps = {
|
|
7
|
+
inputRef?: React.Ref<HTMLButtonElement>;
|
|
7
8
|
label: string;
|
|
8
9
|
items: MultiSelectOptionsProps['options'];
|
|
9
10
|
selectedValues: Set<MultiSelectOption['value']>;
|
|
@@ -18,6 +19,6 @@ export type MultiSelectProps = {
|
|
|
18
19
|
validationMessage?: ValidationMessage;
|
|
19
20
|
} & OverrideClassName<HTMLAttributes<HTMLDivElement>>;
|
|
20
21
|
export declare const MultiSelect: {
|
|
21
|
-
({ id: propsId, label, items, selectedValues, description, onSelectedValuesChange, isOpen, onOpenChange, classNameOverride, validationMessage, ...restProps }: MultiSelectProps): JSX.Element;
|
|
22
|
+
({ id: propsId, label, items, selectedValues, description, onSelectedValuesChange, isOpen, onOpenChange, classNameOverride, validationMessage, inputRef, ...restProps }: MultiSelectProps): JSX.Element;
|
|
22
23
|
displayName: string;
|
|
23
24
|
};
|
|
@@ -4,6 +4,7 @@ import type { EditorContentArray, EditorRows, ToolbarItems } from '../types';
|
|
|
4
4
|
import { ProseMirrorState } from '../utils/prosemirror';
|
|
5
5
|
type BaseRichTextEditorProps = {
|
|
6
6
|
id?: string;
|
|
7
|
+
inputRef?: React.Ref<HTMLElement>;
|
|
7
8
|
onChange: (content: ProseMirrorState.EditorState) => void;
|
|
8
9
|
defaultValue: EditorContentArray;
|
|
9
10
|
controls?: ToolbarItems[];
|
|
@@ -38,7 +39,7 @@ export type RichTextEditorProps = BaseRichTextEditorProps & (WithLabelText | Wit
|
|
|
38
39
|
* {@link https://cultureamp.design/?path=/docs/components-richtexteditor--docs Storybook}
|
|
39
40
|
*/
|
|
40
41
|
export declare const RichTextEditor: {
|
|
41
|
-
({ id, onChange, defaultValue, labelText, "aria-labelledby": labelledBy, "aria-describedby": describedBy, classNameOverride, controls, rows, dataError, onDataError, validationMessage, description, status, ...restProps }: RichTextEditorProps): JSX.Element;
|
|
42
|
+
({ id, inputRef, onChange, defaultValue, labelText, "aria-labelledby": labelledBy, "aria-describedby": describedBy, classNameOverride, controls, rows, dataError, onDataError, validationMessage, description, status, ...restProps }: RichTextEditorProps): JSX.Element;
|
|
42
43
|
displayName: string;
|
|
43
44
|
};
|
|
44
45
|
export {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type EditorState } from 'prosemirror-state';
|
|
2
2
|
import { type CommandOrTransaction } from '../types';
|
|
3
3
|
type RTEOptions = {
|
|
4
|
-
editable
|
|
4
|
+
editable?: boolean;
|
|
5
|
+
inputRef?: React.Ref<HTMLElement>;
|
|
5
6
|
};
|
|
6
7
|
type SetEditableStatus = (status: boolean) => void;
|
|
7
8
|
type UseRichTextEditorReturnValue = [
|
|
@@ -18,5 +19,5 @@ type UseRichTextEditorReturnValue = [
|
|
|
18
19
|
* @param {initialEditorState} ProseMirror state
|
|
19
20
|
* @returns {Array}
|
|
20
21
|
*/
|
|
21
|
-
export declare const useRichTextEditor: (initialEditorState: EditorState, attributes?: Record<string, string>,
|
|
22
|
+
export declare const useRichTextEditor: (initialEditorState: EditorState, attributes?: Record<string, string>, { editable, inputRef }?: RTEOptions) => UseRichTextEditorReturnValue;
|
|
22
23
|
export {};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { type UseFloatingReturn } from '@floating-ui/react-dom';
|
|
3
2
|
import { type SelectProps as AriaSelectProps } from '@react-stately/select';
|
|
4
3
|
import { type Key } from '@react-types/shared';
|
|
5
4
|
import { type OverrideClassName } from "../types/OverrideClassName";
|
|
@@ -12,13 +11,14 @@ export type SingleSelectProps<Option extends SingleSelectOption = SingleSelectOp
|
|
|
12
11
|
*/
|
|
13
12
|
items: SingleSelectItem<Option>[];
|
|
14
13
|
id?: string;
|
|
14
|
+
inputRef?: React.Ref<HTMLButtonElement>;
|
|
15
15
|
/**
|
|
16
16
|
* Optional render function that allows custom rendering of the trigger button.
|
|
17
17
|
* The function receives the `selectToggleProps` and a `ref` to be applied
|
|
18
18
|
* to the button element.
|
|
19
19
|
*/
|
|
20
20
|
trigger?: (selectToggleProps: SelectToggleProps & {
|
|
21
|
-
ref:
|
|
21
|
+
ref: React.Ref<HTMLButtonElement>;
|
|
22
22
|
}) => JSX.Element;
|
|
23
23
|
/**
|
|
24
24
|
* Optional render function that allows custom rendering of the items in the dropdown.
|
|
@@ -56,7 +56,7 @@ export type SingleSelectProps<Option extends SingleSelectOption = SingleSelectOp
|
|
|
56
56
|
* {@link https://cultureamp.design/?path=/docs/components-select--docs Storybook}
|
|
57
57
|
*/
|
|
58
58
|
export declare const SingleSelect: {
|
|
59
|
-
<Option extends SingleSelectOption = SingleSelectOption>({ label, items, id: propsId, trigger, children, status, validationMessage, isReversed, isRequired, isFullWidth, classNameOverride, selectedKey, description, isDisabled, onSelectionChange, portalContainerId, ...restProps }: SingleSelectProps<Option>): JSX.Element;
|
|
59
|
+
<Option extends SingleSelectOption = SingleSelectOption>({ label, items, id: propsId, trigger, children, status, validationMessage, isReversed, isRequired, isFullWidth, classNameOverride, selectedKey, description, isDisabled, onSelectionChange, portalContainerId, inputRef, ...restProps }: SingleSelectProps<Option>): JSX.Element;
|
|
60
60
|
displayName: string;
|
|
61
61
|
Section: {
|
|
62
62
|
<Option extends SingleSelectOption = SingleSelectOption>({ section, }: import("./subcomponents").ListBoxSectionProps<Option>): JSX.Element;
|
package/package.json
CHANGED
package/src/Avatar/Avatar.tsx
CHANGED
|
@@ -91,21 +91,29 @@ const renderInitials = (
|
|
|
91
91
|
const isLongName = initials.length > 2 && size !== 'small'
|
|
92
92
|
const renderFallback = disableInitials || initials === ''
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
<FallbackIcon alt={alt} />
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
94
|
+
if (renderFallback) {
|
|
95
|
+
return <FallbackIcon alt={alt} />
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (isLongName) {
|
|
99
|
+
return (
|
|
100
|
+
<abbr
|
|
101
|
+
className={classnames(styles.initials, styles.longName)}
|
|
102
|
+
title={alt}
|
|
101
103
|
// Ignore Chromatic diffs since the font-size calculation has shown itself to be slightly non-deterministic,
|
|
102
104
|
// causing flaky tests.
|
|
103
|
-
|
|
105
|
+
data-chromatic="ignore"
|
|
106
|
+
>
|
|
107
|
+
<Textfit mode="single" max={getMaxFontSizePixels(size)}>
|
|
104
108
|
{initials}
|
|
105
109
|
</Textfit>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
</abbr>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<abbr className={styles.initials} title={alt}>
|
|
116
|
+
{getInitials(fullName, size === 'small')}
|
|
109
117
|
</abbr>
|
|
110
118
|
)
|
|
111
119
|
}
|
|
@@ -1,26 +1,36 @@
|
|
|
1
|
-
import React, { useRef, type HTMLAttributes, type ReactNode } from 'react'
|
|
1
|
+
import React, { useRef, type ElementType, type HTMLAttributes, type ReactNode } from 'react'
|
|
2
2
|
import classnames from 'classnames'
|
|
3
3
|
import { useFocusable, type FocusableOptions } from 'react-aria'
|
|
4
4
|
import styles from './Focusable.module.css'
|
|
5
5
|
|
|
6
|
-
export type FocusableProps = {
|
|
6
|
+
export type FocusableProps<T extends ElementType = 'div'> = {
|
|
7
7
|
children: ReactNode
|
|
8
|
+
/**
|
|
9
|
+
* The HTML element to render as. Defaults to `div` to avoid an api change
|
|
10
|
+
* but should be set to 'span' if rendering within something like a 'p' tag to avoid invalid HTML.
|
|
11
|
+
*/
|
|
12
|
+
tag?: T
|
|
8
13
|
} & FocusableOptions &
|
|
9
14
|
HTMLAttributes<HTMLDivElement>
|
|
10
15
|
|
|
11
|
-
export const Focusable =
|
|
16
|
+
export const Focusable = <T extends ElementType = 'div'>({
|
|
17
|
+
children,
|
|
18
|
+
className,
|
|
19
|
+
tag,
|
|
20
|
+
...props
|
|
21
|
+
}: FocusableProps<T>): JSX.Element => {
|
|
12
22
|
const ref = useRef<HTMLDivElement>(null)
|
|
13
23
|
const { focusableProps } = useFocusable(props, ref)
|
|
24
|
+
const Element = tag ?? 'div'
|
|
14
25
|
|
|
15
26
|
return (
|
|
16
|
-
<
|
|
27
|
+
<Element
|
|
17
28
|
ref={ref}
|
|
18
29
|
className={classnames(styles.focusableWrapper, className)}
|
|
19
30
|
{...focusableProps}
|
|
20
31
|
data-inline-hidden-content
|
|
21
|
-
// We want the
|
|
32
|
+
// We want the element to be focusable for keyboard users,
|
|
22
33
|
// but screen readers will have the VisuallyHidden content
|
|
23
|
-
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
24
34
|
tabIndex={0}
|
|
25
35
|
// aria-describedby on div doesn't do anthing so we instead render the content in VisuallyHidden from tooltip
|
|
26
36
|
// but because RAC adds it as it assumes it's interactive element we remove it here
|
|
@@ -28,6 +38,6 @@ export const Focusable = ({ children, className, ...props }: FocusableProps): JS
|
|
|
28
38
|
{...props}
|
|
29
39
|
>
|
|
30
40
|
{children}
|
|
31
|
-
</
|
|
41
|
+
</Element>
|
|
32
42
|
)
|
|
33
43
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useId, useRef, type HTMLAttributes } from 'react'
|
|
2
|
+
import { mergeRefs } from '@react-aria/utils'
|
|
2
3
|
import classnames from 'classnames'
|
|
3
4
|
import { type ReactFocusOnProps } from 'react-focus-on/dist/es5/types'
|
|
4
5
|
import { FieldMessage, type FieldMessageProps } from '~components/FieldMessage'
|
|
@@ -14,6 +15,7 @@ import { type MultiSelectOption, type ValidationMessage } from './types'
|
|
|
14
15
|
import styles from './MultiSelect.module.scss'
|
|
15
16
|
|
|
16
17
|
export type MultiSelectProps = {
|
|
18
|
+
inputRef?: React.Ref<HTMLButtonElement>
|
|
17
19
|
label: string
|
|
18
20
|
items: MultiSelectOptionsProps['options']
|
|
19
21
|
selectedValues: Set<MultiSelectOption['value']>
|
|
@@ -39,6 +41,7 @@ export const MultiSelect = ({
|
|
|
39
41
|
onOpenChange,
|
|
40
42
|
classNameOverride,
|
|
41
43
|
validationMessage,
|
|
44
|
+
inputRef,
|
|
42
45
|
...restProps
|
|
43
46
|
}: MultiSelectProps): JSX.Element => {
|
|
44
47
|
const fallbackId = useId()
|
|
@@ -88,7 +91,7 @@ export const MultiSelect = ({
|
|
|
88
91
|
|
|
89
92
|
<div ref={refs.setReference} className={styles.toggleContainer}>
|
|
90
93
|
<MultiSelectToggle
|
|
91
|
-
ref={toggleButtonRef}
|
|
94
|
+
ref={mergeRefs(toggleButtonRef, inputRef)}
|
|
92
95
|
id={`${id}--toggle`}
|
|
93
96
|
aria-labelledby={`${id}--label`}
|
|
94
97
|
aria-describedby={`${validationId} ${descriptionId}`}
|
|
@@ -22,6 +22,7 @@ import styles from './RichTextEditor.module.scss'
|
|
|
22
22
|
|
|
23
23
|
type BaseRichTextEditorProps = {
|
|
24
24
|
id?: string
|
|
25
|
+
inputRef?: React.Ref<HTMLElement>
|
|
25
26
|
onChange: (content: ProseMirrorState.EditorState) => void
|
|
26
27
|
defaultValue: EditorContentArray
|
|
27
28
|
controls?: ToolbarItems[]
|
|
@@ -60,6 +61,7 @@ export type RichTextEditorProps = BaseRichTextEditorProps & (WithLabelText | Wit
|
|
|
60
61
|
*/
|
|
61
62
|
export const RichTextEditor = ({
|
|
62
63
|
id,
|
|
64
|
+
inputRef,
|
|
63
65
|
onChange,
|
|
64
66
|
defaultValue,
|
|
65
67
|
labelText,
|
|
@@ -105,6 +107,7 @@ export const RichTextEditor = ({
|
|
|
105
107
|
'role': 'textbox',
|
|
106
108
|
'aria-describedby': ariaDescribedBy,
|
|
107
109
|
},
|
|
110
|
+
{ inputRef },
|
|
108
111
|
)
|
|
109
112
|
} catch {
|
|
110
113
|
return new Error('Bad data error')
|
|
@@ -32,7 +32,7 @@ describe('createRichTextEditor()', () => {
|
|
|
32
32
|
initialEditorState: testEditorState,
|
|
33
33
|
})
|
|
34
34
|
|
|
35
|
-
expect(Object.keys(returnValue)).toEqual(['destroy', 'dispatchTransaction'])
|
|
35
|
+
expect(Object.keys(returnValue)).toEqual(['destroy', 'dispatchTransaction', 'dom'])
|
|
36
36
|
expect(typeof returnValue.destroy).toEqual('function')
|
|
37
37
|
expect(typeof returnValue.dispatchTransaction).toEqual('function')
|
|
38
38
|
})
|
|
@@ -5,6 +5,7 @@ import { type CommandOrTransaction } from './types'
|
|
|
5
5
|
type EditorAPI = {
|
|
6
6
|
destroy: () => void
|
|
7
7
|
dispatchTransaction: (maybeCommand: CommandOrTransaction) => void
|
|
8
|
+
dom: HTMLElement
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
type EditorArgs = {
|
|
@@ -60,5 +61,6 @@ export const createRichTextEditor = ({
|
|
|
60
61
|
return {
|
|
61
62
|
destroy: () => editorView.destroy(),
|
|
62
63
|
dispatchTransaction: dispatchCommandOrTransaction,
|
|
64
|
+
dom: editorView.dom,
|
|
63
65
|
}
|
|
64
66
|
}
|
|
@@ -10,9 +10,11 @@ const user = userEvent.setup()
|
|
|
10
10
|
const Scenario = ({
|
|
11
11
|
onChange = () => undefined,
|
|
12
12
|
editable = true,
|
|
13
|
+
inputRef,
|
|
13
14
|
}: {
|
|
14
15
|
onChange?: (editorState: EditorState) => void
|
|
15
16
|
editable?: boolean
|
|
17
|
+
inputRef?: React.Ref<HTMLElement>
|
|
16
18
|
}): JSX.Element => {
|
|
17
19
|
const command: Command = (state, dispatch) => {
|
|
18
20
|
// Insert text at the current selection point, which is the start because
|
|
@@ -25,7 +27,7 @@ const Scenario = ({
|
|
|
25
27
|
const [ref, editorState, dispatchTransaction, setEditableStatus] = useRichTextEditor(
|
|
26
28
|
testEditorState,
|
|
27
29
|
{ 'aria-labelledby': 'label-ref-id', 'data-testid': '12345678' },
|
|
28
|
-
{ editable },
|
|
30
|
+
{ editable, inputRef },
|
|
29
31
|
)
|
|
30
32
|
|
|
31
33
|
useEffect(() => {
|
|
@@ -151,14 +153,17 @@ describe('useRichTextEditor()', () => {
|
|
|
151
153
|
})
|
|
152
154
|
})
|
|
153
155
|
|
|
154
|
-
it('
|
|
155
|
-
const
|
|
156
|
+
it('assigns ref on mount and cleans up DOM/ref on unmount', async () => {
|
|
157
|
+
const inputRef = React.createRef<HTMLElement>()
|
|
158
|
+
const { unmount } = render(<Scenario inputRef={inputRef} />)
|
|
156
159
|
|
|
157
160
|
const editor = screen.getByTestId('testid--editor')
|
|
158
161
|
expect(editor).toBeInTheDocument()
|
|
162
|
+
expect(inputRef.current).toHaveAttribute('contenteditable', 'true')
|
|
159
163
|
|
|
160
164
|
unmount()
|
|
161
165
|
|
|
162
166
|
expect(screen.queryByTestId('testid--editor')).not.toBeInTheDocument()
|
|
167
|
+
expect(inputRef.current).toBeNull()
|
|
163
168
|
})
|
|
164
169
|
})
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useCallback, useRef, useState } from 'react'
|
|
2
2
|
import { type EditorState } from 'prosemirror-state'
|
|
3
|
+
import { isRefObject } from '~components/utils/isRefObject'
|
|
3
4
|
import { createRichTextEditor } from '../createRichTextEditor'
|
|
4
5
|
import { type CommandOrTransaction } from '../types'
|
|
5
6
|
|
|
6
7
|
type RTEOptions = {
|
|
7
|
-
editable
|
|
8
|
+
editable?: boolean
|
|
9
|
+
inputRef?: React.Ref<HTMLElement>
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
type SetEditableStatus = (status: boolean) => void
|
|
@@ -30,12 +32,8 @@ export const useRichTextEditor = (
|
|
|
30
32
|
* Pass in HTML attributes into the parent RTE node
|
|
31
33
|
*/
|
|
32
34
|
attributes?: Record<string, string>,
|
|
33
|
-
|
|
35
|
+
{ editable = true, inputRef }: RTEOptions = {},
|
|
34
36
|
): UseRichTextEditorReturnValue => {
|
|
35
|
-
options = {
|
|
36
|
-
editable: true,
|
|
37
|
-
...options,
|
|
38
|
-
}
|
|
39
37
|
const [editorState, setEditorState] = useState<EditorState>(initialEditorState)
|
|
40
38
|
// Refs to hold the methods returned from ProseMirror’s initialization
|
|
41
39
|
const destroyEditorRef = useRef<() => void>()
|
|
@@ -53,7 +51,11 @@ export const useRichTextEditor = (
|
|
|
53
51
|
)
|
|
54
52
|
|
|
55
53
|
// Hold editableStatus as a ref so we can toggle its status
|
|
56
|
-
const editableStatusRef = useRef<boolean>(
|
|
54
|
+
const editableStatusRef = useRef<boolean>(editable)
|
|
55
|
+
|
|
56
|
+
// Stable ref to avoid recreating editorRef when inputRef callback identity changes
|
|
57
|
+
const inputRefRef = useRef(inputRef)
|
|
58
|
+
inputRefRef.current = inputRef
|
|
57
59
|
const setEditableStatus = useCallback<SetEditableStatus>(
|
|
58
60
|
(status) => {
|
|
59
61
|
editableStatusRef.current = status
|
|
@@ -76,6 +78,11 @@ export const useRichTextEditor = (
|
|
|
76
78
|
destroyEditorRef.current()
|
|
77
79
|
destroyEditorRef.current = undefined
|
|
78
80
|
}
|
|
81
|
+
if (inputRefRef.current && isRefObject(inputRefRef.current)) {
|
|
82
|
+
;(inputRefRef.current as React.MutableRefObject<HTMLElement | null>).current = null
|
|
83
|
+
} else {
|
|
84
|
+
inputRefRef.current?.(null)
|
|
85
|
+
}
|
|
79
86
|
return
|
|
80
87
|
}
|
|
81
88
|
|
|
@@ -88,6 +95,12 @@ export const useRichTextEditor = (
|
|
|
88
95
|
})
|
|
89
96
|
destroyEditorRef.current = instance.destroy
|
|
90
97
|
dispatchTransactionRef.current = instance.dispatchTransaction
|
|
98
|
+
|
|
99
|
+
if (inputRefRef.current && isRefObject(inputRefRef.current)) {
|
|
100
|
+
;(inputRefRef.current as React.MutableRefObject<HTMLElement | null>).current = instance.dom
|
|
101
|
+
} else {
|
|
102
|
+
inputRefRef.current?.(instance.dom)
|
|
103
|
+
}
|
|
91
104
|
},
|
|
92
105
|
|
|
93
106
|
// Including editorState in the dependencies here will cause an endless
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useEffect, useId, useState } from 'react'
|
|
2
|
-
import { type UseFloatingReturn } from '@floating-ui/react-dom'
|
|
3
2
|
import { useButton } from '@react-aria/button'
|
|
4
3
|
import { HiddenSelect, useSelect } from '@react-aria/select'
|
|
4
|
+
import { mergeRefs } from '@react-aria/utils'
|
|
5
5
|
import { useSelectState, type SelectProps as AriaSelectProps } from '@react-stately/select'
|
|
6
6
|
import { type Key } from '@react-types/shared'
|
|
7
7
|
import classnames from 'classnames'
|
|
@@ -32,6 +32,7 @@ export type SingleSelectProps<Option extends SingleSelectOption = SingleSelectOp
|
|
|
32
32
|
*/
|
|
33
33
|
items: SingleSelectItem<Option>[]
|
|
34
34
|
id?: string
|
|
35
|
+
inputRef?: React.Ref<HTMLButtonElement>
|
|
35
36
|
/**
|
|
36
37
|
* Optional render function that allows custom rendering of the trigger button.
|
|
37
38
|
* The function receives the `selectToggleProps` and a `ref` to be applied
|
|
@@ -39,7 +40,7 @@ export type SingleSelectProps<Option extends SingleSelectOption = SingleSelectOp
|
|
|
39
40
|
*/
|
|
40
41
|
trigger?: (
|
|
41
42
|
selectToggleProps: SelectToggleProps & {
|
|
42
|
-
ref:
|
|
43
|
+
ref: React.Ref<HTMLButtonElement>
|
|
43
44
|
},
|
|
44
45
|
) => JSX.Element
|
|
45
46
|
/**
|
|
@@ -95,6 +96,7 @@ export const SingleSelect = <Option extends SingleSelectOption = SingleSelectOpt
|
|
|
95
96
|
isDisabled,
|
|
96
97
|
onSelectionChange,
|
|
97
98
|
portalContainerId,
|
|
99
|
+
inputRef,
|
|
98
100
|
...restProps
|
|
99
101
|
}: SingleSelectProps<Option>): JSX.Element => {
|
|
100
102
|
const { refs } = useFloating<HTMLButtonElement>()
|
|
@@ -153,7 +155,7 @@ export const SingleSelect = <Option extends SingleSelectOption = SingleSelectOpt
|
|
|
153
155
|
status,
|
|
154
156
|
'isDisabled': triggerProps.isDisabled,
|
|
155
157
|
isReversed,
|
|
156
|
-
'ref': refs.setReference,
|
|
158
|
+
'ref': mergeRefs(inputRef, refs.setReference),
|
|
157
159
|
'aria-describedby': classnames(validationMessage && validationId, description && descriptionId),
|
|
158
160
|
'aria-required': isRequired,
|
|
159
161
|
}
|