@kwiz/fluentui 1.0.73 → 1.0.75
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/.github/workflows/npm-publish.yml +24 -24
- package/LICENSE +21 -21
- package/README.md +53 -53
- package/dist/@types/forwardRef.d.ts +0 -0
- package/dist/@types/forwardRef.js +1 -0
- package/dist/@types/forwardRef.js.map +1 -0
- package/dist/controls/error-boundary copy.d.ts +23 -0
- package/dist/controls/error-boundary copy.js +33 -0
- package/dist/controls/error-boundary copy.js.map +1 -0
- package/dist/controls/menu.js +2 -2
- package/dist/controls/menu.js.map +1 -1
- package/dist/controls/search.js +19 -11
- package/dist/controls/search.js.map +1 -1
- package/dist/controls/svg.js +21 -21
- package/dist/controls/svg.js.map +1 -1
- package/dist/helpers/common.d.ts +4 -0
- package/dist/helpers/common.js +2 -0
- package/dist/helpers/common.js.map +1 -0
- package/dist/helpers/context.d.ts +26 -0
- package/dist/helpers/context.js +15 -0
- package/dist/helpers/context.js.map +1 -0
- package/dist/helpers/drag-drop/exports.d.ts +12 -0
- package/dist/helpers/drag-drop/exports.js +3 -0
- package/dist/helpers/drag-drop/exports.js.map +1 -0
- package/dist/helpers/exports.d.ts +7 -0
- package/dist/helpers/exports.js +8 -0
- package/dist/helpers/exports.js.map +1 -0
- package/dist/helpers/use-editable-control.d.ts +1 -1
- package/dist/helpers/use-editable-control.js.map +1 -1
- package/package.json +85 -84
- package/src/_modules/config.ts +9 -9
- package/src/_modules/constants.ts +3 -3
- package/src/controls/ColorPickerDialog.tsx +83 -83
- package/src/controls/accordion.tsx +62 -62
- package/src/controls/button.tsx +180 -180
- package/src/controls/canvas/CustomEventTargetBase.ts +32 -32
- package/src/controls/canvas/DrawPad.tsx +296 -296
- package/src/controls/canvas/DrawPadManager.ts +694 -694
- package/src/controls/canvas/bezier.ts +109 -109
- package/src/controls/canvas/point.ts +44 -44
- package/src/controls/card-list.tsx +31 -31
- package/src/controls/card.tsx +77 -77
- package/src/controls/centered.tsx +14 -14
- package/src/controls/date.tsx +87 -87
- package/src/controls/diagram-picker.tsx +96 -96
- package/src/controls/divider.tsx +15 -15
- package/src/controls/dropdown.tsx +66 -66
- package/src/controls/error-boundary.tsx +41 -41
- package/src/controls/field-editor.tsx +42 -42
- package/src/controls/file-upload.tsx +155 -155
- package/src/controls/horizontal.tsx +48 -48
- package/src/controls/html-editor/editor.tsx +182 -182
- package/src/controls/index.ts +33 -33
- package/src/controls/input.tsx +160 -160
- package/src/controls/kwizoverflow.tsx +106 -106
- package/src/controls/list.tsx +119 -119
- package/src/controls/loading.tsx +10 -10
- package/src/controls/menu.tsx +173 -173
- package/src/controls/merge-text.tsx +126 -126
- package/src/controls/please-wait.tsx +32 -32
- package/src/controls/progress-bar.tsx +109 -109
- package/src/controls/prompt.tsx +121 -121
- package/src/controls/qrcode.tsx +36 -36
- package/src/controls/search.tsx +71 -61
- package/src/controls/section.tsx +133 -133
- package/src/controls/svg.tsx +138 -138
- package/src/controls/toolbar.tsx +46 -46
- package/src/controls/vertical-content.tsx +49 -49
- package/src/controls/vertical.tsx +42 -42
- package/src/helpers/block-nav.tsx +88 -88
- package/src/helpers/context-const.ts +29 -29
- package/src/helpers/context-export.tsx +77 -77
- package/src/helpers/context-internal.ts +13 -13
- package/src/helpers/drag-drop/drag-drop-container.tsx +53 -53
- package/src/helpers/drag-drop/drag-drop-context-internal.tsx +9 -9
- package/src/helpers/drag-drop/drag-drop-context.tsx +61 -61
- package/src/helpers/drag-drop/drag-drop.types.ts +21 -21
- package/src/helpers/drag-drop/index.ts +12 -12
- package/src/helpers/drag-drop/readme.md +75 -75
- package/src/helpers/drag-drop/use-draggable.ts +47 -47
- package/src/helpers/drag-drop/use-droppable.ts +38 -38
- package/src/helpers/forwardRef.ts +7 -7
- package/src/helpers/hooks-events.ts +149 -149
- package/src/helpers/hooks.tsx +141 -141
- package/src/helpers/index.ts +8 -8
- package/src/helpers/use-alerts.tsx +74 -74
- package/src/helpers/use-editable-control.tsx +37 -37
- package/src/helpers/use-toast.tsx +29 -29
- package/src/index.ts +2 -2
- package/src/styles/index.ts +1 -1
- package/src/styles/styles.ts +104 -104
- package/src/styles/theme.ts +90 -90
package/src/controls/input.tsx
CHANGED
@@ -1,161 +1,161 @@
|
|
1
|
-
import { GriffelStyle, Input, InputOnChangeData, InputProps, Label, Link, makeStyles, mergeClasses, Textarea, TextareaOnChangeData, TextareaProps } from '@fluentui/react-components';
|
2
|
-
import { isFunction, isNotEmptyArray, isNullOrEmptyString, isNullOrNaN, isNullOrUndefined, isNumber, pasteTextAtCursor, stopEvent } from '@kwiz/common';
|
3
|
-
import React, { useCallback, useEffect } from 'react';
|
4
|
-
import { useEffectOnlyOnMount, useRefWithState } from '../helpers';
|
5
|
-
import { useKWIZFluentContext } from '../helpers/context-internal';
|
6
|
-
import { useCommonStyles } from '../styles/styles';
|
7
|
-
import { Horizontal } from './horizontal';
|
8
|
-
import { MenuEx } from './menu';
|
9
|
-
import { Section } from './section';
|
10
|
-
import { Vertical } from './vertical';
|
11
|
-
|
12
|
-
|
13
|
-
interface IProps extends InputProps {
|
14
|
-
/** fire on enter */
|
15
|
-
onOK?: () => void;
|
16
|
-
/** fire on escape */
|
17
|
-
onCancel?: () => void;
|
18
|
-
tokens?: { title: string; value: string; replace?: boolean; }[];
|
19
|
-
tokenMenuLabel?: string;
|
20
|
-
}
|
21
|
-
export const InputEx: React.FunctionComponent<React.PropsWithChildren<IProps>> = (props) => {
|
22
|
-
const ctx = useKWIZFluentContext();
|
23
|
-
const input = <Input appearance={ctx.inputAppearance} {...props}
|
24
|
-
onKeyDown={isFunction(props.onOK) || isFunction(props.onCancel)
|
25
|
-
? e => {
|
26
|
-
if (e.key === "Enter") props.onOK?.();
|
27
|
-
if (e.key === "Escape") props.onCancel?.();
|
28
|
-
}
|
29
|
-
: undefined
|
30
|
-
}
|
31
|
-
/>;
|
32
|
-
return (
|
33
|
-
isNotEmptyArray(props.tokens)
|
34
|
-
? <Vertical nogap>
|
35
|
-
{input}
|
36
|
-
<Horizontal nogap>
|
37
|
-
<Section main />
|
38
|
-
<MenuEx trigger={<Link>{props.tokenMenuLabel || "tokens"}</Link>} items={props.tokens.map(token =>
|
39
|
-
({
|
40
|
-
title: token.title, onClick: () => {
|
41
|
-
let newValue = props.value || "";
|
42
|
-
if (token.replace) {
|
43
|
-
newValue = token.value;
|
44
|
-
}
|
45
|
-
else {
|
46
|
-
if (isNullOrEmptyString(props.value))
|
47
|
-
newValue = token.value;
|
48
|
-
else
|
49
|
-
newValue += ` ${token.value}`;
|
50
|
-
}
|
51
|
-
props.onChange(null, {
|
52
|
-
value: newValue
|
53
|
-
});
|
54
|
-
}
|
55
|
-
}))} />
|
56
|
-
</Horizontal>
|
57
|
-
</Vertical>
|
58
|
-
: input
|
59
|
-
);
|
60
|
-
}
|
61
|
-
|
62
|
-
const fullSize: GriffelStyle = {
|
63
|
-
width: '100% !important',
|
64
|
-
maxHeight: '100% !important'
|
65
|
-
};
|
66
|
-
const useStyles = makeStyles({
|
67
|
-
fullSizeTextArea: {
|
68
|
-
...fullSize,
|
69
|
-
['& > textarea']: fullSize
|
70
|
-
},
|
71
|
-
})
|
72
|
-
|
73
|
-
interface IPropsTextArea extends TextareaProps {
|
74
|
-
fullSize?: boolean;
|
75
|
-
/** recalc the height to grow to show all text */
|
76
|
-
growNoShrink?: boolean;
|
77
|
-
allowTab?: boolean;
|
78
|
-
/** fire on enter */
|
79
|
-
onOK?: () => void;
|
80
|
-
/** fire on escape */
|
81
|
-
onCancel?: () => void;
|
82
|
-
onValueChange?: (e: React.ChangeEvent<HTMLTextAreaElement> | React.KeyboardEvent<HTMLTextAreaElement>, d: {
|
83
|
-
value: string;
|
84
|
-
elm: HTMLTextAreaElement;
|
85
|
-
}) => void;
|
86
|
-
}
|
87
|
-
export const TextAreaEx: React.FunctionComponent<React.PropsWithChildren<IPropsTextArea>> = (props) => {
|
88
|
-
const cssNames = useStyles();
|
89
|
-
let css: string[] = [];
|
90
|
-
|
91
|
-
if (props.fullSize) css.push(cssNames.fullSizeTextArea);
|
92
|
-
const textAreaRef = useRefWithState<HTMLTextAreaElement>(null);
|
93
|
-
const recalcHeight = React.useCallback(() => {
|
94
|
-
if (textAreaRef.ref.current && props.growNoShrink) {
|
95
|
-
if (textAreaRef.ref.current.scrollHeight > textAreaRef.ref.current.clientHeight)
|
96
|
-
textAreaRef.ref.current.style.minHeight = textAreaRef.ref.current.scrollHeight + 'px';
|
97
|
-
}
|
98
|
-
}, useEffectOnlyOnMount);
|
99
|
-
|
100
|
-
useEffect(() => { recalcHeight(); }, [textAreaRef.value]);
|
101
|
-
|
102
|
-
const onChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement> | React.KeyboardEvent<HTMLTextAreaElement>, d: TextareaOnChangeData) => {
|
103
|
-
props.onValueChange?.(e, { value: d.value, elm: textAreaRef.ref.current });
|
104
|
-
recalcHeight();
|
105
|
-
}, [props.onChange]);
|
106
|
-
|
107
|
-
const needOnKeyDown = props.allowTab || isFunction(props.onOK) || isFunction(props.onCancel);
|
108
|
-
|
109
|
-
let style: React.CSSProperties = { height: '100%', ...props.style };
|
110
|
-
|
111
|
-
return (
|
112
|
-
<Textarea ref={textAreaRef.set} className={mergeClasses(...css)} {...props} style={style}
|
113
|
-
onKeyDown={needOnKeyDown ? (e) => {
|
114
|
-
if (props.allowTab && e.key === "Tab") {
|
115
|
-
stopEvent(e);
|
116
|
-
const textArea = e.target as HTMLTextAreaElement;
|
117
|
-
pasteTextAtCursor(textArea, "\t");
|
118
|
-
onChange(e, { value: textArea.value });
|
119
|
-
return;
|
120
|
-
}
|
121
|
-
if (e.key === "Enter") props.onOK?.();
|
122
|
-
if (e.key === "Escape") props.onCancel?.();
|
123
|
-
props.onKeyDown?.(e);
|
124
|
-
} : props.onKeyDown}
|
125
|
-
onChange={(e, d) => {
|
126
|
-
props.onChange?.(e, d);
|
127
|
-
onChange(e, d);
|
128
|
-
}} />
|
129
|
-
);
|
130
|
-
}
|
131
|
-
|
132
|
-
|
133
|
-
interface INumberProps extends Omit<IProps, "value" | "onChange" | "defaultValue" | "inputMode"> {
|
134
|
-
defaultValue?: number;
|
135
|
-
onChange: (value: number) => void;
|
136
|
-
allowDecimals?: boolean;
|
137
|
-
/** if sent true - onChange will only be called when a valid non-empty value is being set */
|
138
|
-
required?: boolean;
|
139
|
-
}
|
140
|
-
export const InputNumberEx: React.FunctionComponent<React.PropsWithChildren<INumberProps>> = (props) => {
|
141
|
-
const commonStyles = useCommonStyles();
|
142
|
-
const [valueStr, setValueStr] = React.useState(isNumber(props.defaultValue) ? `${props.defaultValue}` : '');
|
143
|
-
const [isValid, setIsValid] = React.useState(true);
|
144
|
-
const onChange = React.useCallback((ev: React.ChangeEvent<HTMLInputElement>, data: InputOnChangeData) => {
|
145
|
-
const newValue = data.value;
|
146
|
-
setValueStr(newValue);//update text box anyways
|
147
|
-
const asNumber = props.allowDecimals ? parseFloat(newValue) : parseInt(newValue, 10);
|
148
|
-
const isValid = props.required ? !isNullOrNaN(asNumber) : isNullOrUndefined(asNumber) || !isNaN(asNumber);
|
149
|
-
setIsValid(isValid);
|
150
|
-
props.onChange(isValid ? asNumber : null);
|
151
|
-
}, [props.allowDecimals, props.onChange, props.required]);
|
152
|
-
|
153
|
-
const passProps: IProps = { ...props, defaultValue: undefined, value: undefined, onChange: undefined };
|
154
|
-
|
155
|
-
return (
|
156
|
-
<Vertical nogap>
|
157
|
-
<InputEx inputMode={props.allowDecimals ? "decimal" : "numeric"} {...passProps} value={valueStr} onChange={onChange} />
|
158
|
-
{!isValid && <Label className={commonStyles.validationLabel}>this is not a valid value</Label>}
|
159
|
-
</Vertical>
|
160
|
-
);
|
1
|
+
import { GriffelStyle, Input, InputOnChangeData, InputProps, Label, Link, makeStyles, mergeClasses, Textarea, TextareaOnChangeData, TextareaProps } from '@fluentui/react-components';
|
2
|
+
import { isFunction, isNotEmptyArray, isNullOrEmptyString, isNullOrNaN, isNullOrUndefined, isNumber, pasteTextAtCursor, stopEvent } from '@kwiz/common';
|
3
|
+
import React, { useCallback, useEffect } from 'react';
|
4
|
+
import { useEffectOnlyOnMount, useRefWithState } from '../helpers';
|
5
|
+
import { useKWIZFluentContext } from '../helpers/context-internal';
|
6
|
+
import { useCommonStyles } from '../styles/styles';
|
7
|
+
import { Horizontal } from './horizontal';
|
8
|
+
import { MenuEx } from './menu';
|
9
|
+
import { Section } from './section';
|
10
|
+
import { Vertical } from './vertical';
|
11
|
+
|
12
|
+
|
13
|
+
interface IProps extends InputProps {
|
14
|
+
/** fire on enter */
|
15
|
+
onOK?: () => void;
|
16
|
+
/** fire on escape */
|
17
|
+
onCancel?: () => void;
|
18
|
+
tokens?: { title: string; value: string; replace?: boolean; }[];
|
19
|
+
tokenMenuLabel?: string;
|
20
|
+
}
|
21
|
+
export const InputEx: React.FunctionComponent<React.PropsWithChildren<IProps>> = (props) => {
|
22
|
+
const ctx = useKWIZFluentContext();
|
23
|
+
const input = <Input appearance={ctx.inputAppearance} {...props}
|
24
|
+
onKeyDown={isFunction(props.onOK) || isFunction(props.onCancel)
|
25
|
+
? e => {
|
26
|
+
if (e.key === "Enter") props.onOK?.();
|
27
|
+
if (e.key === "Escape") props.onCancel?.();
|
28
|
+
}
|
29
|
+
: undefined
|
30
|
+
}
|
31
|
+
/>;
|
32
|
+
return (
|
33
|
+
isNotEmptyArray(props.tokens)
|
34
|
+
? <Vertical nogap>
|
35
|
+
{input}
|
36
|
+
<Horizontal nogap>
|
37
|
+
<Section main />
|
38
|
+
<MenuEx trigger={<Link>{props.tokenMenuLabel || "tokens"}</Link>} items={props.tokens.map(token =>
|
39
|
+
({
|
40
|
+
title: token.title, onClick: () => {
|
41
|
+
let newValue = props.value || "";
|
42
|
+
if (token.replace) {
|
43
|
+
newValue = token.value;
|
44
|
+
}
|
45
|
+
else {
|
46
|
+
if (isNullOrEmptyString(props.value))
|
47
|
+
newValue = token.value;
|
48
|
+
else
|
49
|
+
newValue += ` ${token.value}`;
|
50
|
+
}
|
51
|
+
props.onChange(null, {
|
52
|
+
value: newValue
|
53
|
+
});
|
54
|
+
}
|
55
|
+
}))} />
|
56
|
+
</Horizontal>
|
57
|
+
</Vertical>
|
58
|
+
: input
|
59
|
+
);
|
60
|
+
}
|
61
|
+
|
62
|
+
const fullSize: GriffelStyle = {
|
63
|
+
width: '100% !important',
|
64
|
+
maxHeight: '100% !important'
|
65
|
+
};
|
66
|
+
const useStyles = makeStyles({
|
67
|
+
fullSizeTextArea: {
|
68
|
+
...fullSize,
|
69
|
+
['& > textarea']: fullSize
|
70
|
+
},
|
71
|
+
})
|
72
|
+
|
73
|
+
interface IPropsTextArea extends TextareaProps {
|
74
|
+
fullSize?: boolean;
|
75
|
+
/** recalc the height to grow to show all text */
|
76
|
+
growNoShrink?: boolean;
|
77
|
+
allowTab?: boolean;
|
78
|
+
/** fire on enter */
|
79
|
+
onOK?: () => void;
|
80
|
+
/** fire on escape */
|
81
|
+
onCancel?: () => void;
|
82
|
+
onValueChange?: (e: React.ChangeEvent<HTMLTextAreaElement> | React.KeyboardEvent<HTMLTextAreaElement>, d: {
|
83
|
+
value: string;
|
84
|
+
elm: HTMLTextAreaElement;
|
85
|
+
}) => void;
|
86
|
+
}
|
87
|
+
export const TextAreaEx: React.FunctionComponent<React.PropsWithChildren<IPropsTextArea>> = (props) => {
|
88
|
+
const cssNames = useStyles();
|
89
|
+
let css: string[] = [];
|
90
|
+
|
91
|
+
if (props.fullSize) css.push(cssNames.fullSizeTextArea);
|
92
|
+
const textAreaRef = useRefWithState<HTMLTextAreaElement>(null);
|
93
|
+
const recalcHeight = React.useCallback(() => {
|
94
|
+
if (textAreaRef.ref.current && props.growNoShrink) {
|
95
|
+
if (textAreaRef.ref.current.scrollHeight > textAreaRef.ref.current.clientHeight)
|
96
|
+
textAreaRef.ref.current.style.minHeight = textAreaRef.ref.current.scrollHeight + 'px';
|
97
|
+
}
|
98
|
+
}, useEffectOnlyOnMount);
|
99
|
+
|
100
|
+
useEffect(() => { recalcHeight(); }, [textAreaRef.value]);
|
101
|
+
|
102
|
+
const onChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement> | React.KeyboardEvent<HTMLTextAreaElement>, d: TextareaOnChangeData) => {
|
103
|
+
props.onValueChange?.(e, { value: d.value, elm: textAreaRef.ref.current });
|
104
|
+
recalcHeight();
|
105
|
+
}, [props.onChange]);
|
106
|
+
|
107
|
+
const needOnKeyDown = props.allowTab || isFunction(props.onOK) || isFunction(props.onCancel);
|
108
|
+
|
109
|
+
let style: React.CSSProperties = { height: '100%', ...props.style };
|
110
|
+
|
111
|
+
return (
|
112
|
+
<Textarea ref={textAreaRef.set} className={mergeClasses(...css)} {...props} style={style}
|
113
|
+
onKeyDown={needOnKeyDown ? (e) => {
|
114
|
+
if (props.allowTab && e.key === "Tab") {
|
115
|
+
stopEvent(e);
|
116
|
+
const textArea = e.target as HTMLTextAreaElement;
|
117
|
+
pasteTextAtCursor(textArea, "\t");
|
118
|
+
onChange(e, { value: textArea.value });
|
119
|
+
return;
|
120
|
+
}
|
121
|
+
if (e.key === "Enter") props.onOK?.();
|
122
|
+
if (e.key === "Escape") props.onCancel?.();
|
123
|
+
props.onKeyDown?.(e);
|
124
|
+
} : props.onKeyDown}
|
125
|
+
onChange={(e, d) => {
|
126
|
+
props.onChange?.(e, d);
|
127
|
+
onChange(e, d);
|
128
|
+
}} />
|
129
|
+
);
|
130
|
+
}
|
131
|
+
|
132
|
+
|
133
|
+
interface INumberProps extends Omit<IProps, "value" | "onChange" | "defaultValue" | "inputMode"> {
|
134
|
+
defaultValue?: number;
|
135
|
+
onChange: (value: number) => void;
|
136
|
+
allowDecimals?: boolean;
|
137
|
+
/** if sent true - onChange will only be called when a valid non-empty value is being set */
|
138
|
+
required?: boolean;
|
139
|
+
}
|
140
|
+
export const InputNumberEx: React.FunctionComponent<React.PropsWithChildren<INumberProps>> = (props) => {
|
141
|
+
const commonStyles = useCommonStyles();
|
142
|
+
const [valueStr, setValueStr] = React.useState(isNumber(props.defaultValue) ? `${props.defaultValue}` : '');
|
143
|
+
const [isValid, setIsValid] = React.useState(true);
|
144
|
+
const onChange = React.useCallback((ev: React.ChangeEvent<HTMLInputElement>, data: InputOnChangeData) => {
|
145
|
+
const newValue = data.value;
|
146
|
+
setValueStr(newValue);//update text box anyways
|
147
|
+
const asNumber = props.allowDecimals ? parseFloat(newValue) : parseInt(newValue, 10);
|
148
|
+
const isValid = props.required ? !isNullOrNaN(asNumber) : isNullOrUndefined(asNumber) || !isNaN(asNumber);
|
149
|
+
setIsValid(isValid);
|
150
|
+
props.onChange(isValid ? asNumber : null);
|
151
|
+
}, [props.allowDecimals, props.onChange, props.required]);
|
152
|
+
|
153
|
+
const passProps: IProps = { ...props, defaultValue: undefined, value: undefined, onChange: undefined };
|
154
|
+
|
155
|
+
return (
|
156
|
+
<Vertical nogap>
|
157
|
+
<InputEx inputMode={props.allowDecimals ? "decimal" : "numeric"} {...passProps} value={valueStr} onChange={onChange} />
|
158
|
+
{!isValid && <Label className={commonStyles.validationLabel}>this is not a valid value</Label>}
|
159
|
+
</Vertical>
|
160
|
+
);
|
161
161
|
}
|
@@ -1,107 +1,107 @@
|
|
1
|
-
import {
|
2
|
-
Menu, MenuButton, MenuItem, MenuList, MenuPopover, MenuTrigger, Overflow, OverflowItem,
|
3
|
-
useIsOverflowItemVisible, useOverflowMenu
|
4
|
-
} from "@fluentui/react-components";
|
5
|
-
import { MoreHorizontalFilled } from "@fluentui/react-icons";
|
6
|
-
import { isNumber } from '@kwiz/common';
|
7
|
-
import { useKWIZFluentContext } from "../helpers/context-internal";
|
8
|
-
|
9
|
-
interface IProps<ItemType> {
|
10
|
-
/** you cannot have a menu with trigger in overflow items. put those in groupWrapper controls before/after rendering children. */
|
11
|
-
items: ItemType[];
|
12
|
-
getKey: (item: ItemType, index: number) => string;
|
13
|
-
getPriority?: (item: ItemType, index: number) => number;
|
14
|
-
renderItem: (item: ItemType, index: number, overflow?: boolean) => JSX.Element;
|
15
|
-
groupWrapper?: (children: React.ReactNode) => JSX.Element;
|
16
|
-
menuRef?: React.RefObject<HTMLButtonElement>;
|
17
|
-
menuWrapper?: (children: React.ReactNode) => JSX.Element;
|
18
|
-
menuTrigger?: (ref: React.RefObject<HTMLButtonElement>, overflowCount: number) => JSX.Element;
|
19
|
-
className?: string;
|
20
|
-
}
|
21
|
-
const OverflowMenu = <ItemType,>(props: IProps<ItemType>) => {
|
22
|
-
const ctx = useKWIZFluentContext();
|
23
|
-
|
24
|
-
const { ref, isOverflowing, overflowCount } =
|
25
|
-
useOverflowMenu<HTMLButtonElement>();
|
26
|
-
|
27
|
-
if (!isOverflowing) {
|
28
|
-
return null;
|
29
|
-
}
|
30
|
-
|
31
|
-
let menu = <Menu mountNode={ctx.mountNode}>
|
32
|
-
<MenuTrigger disableButtonEnhancement>
|
33
|
-
{props.menuTrigger
|
34
|
-
? props.menuTrigger(props.menuRef || ref, overflowCount)
|
35
|
-
: <MenuButton
|
36
|
-
icon={<MoreHorizontalFilled />}
|
37
|
-
ref={props.menuRef || ref}
|
38
|
-
aria-label="More items"
|
39
|
-
appearance="subtle"
|
40
|
-
/>}
|
41
|
-
</MenuTrigger>
|
42
|
-
<MenuPopover>
|
43
|
-
<MenuList>
|
44
|
-
{props.items.map((item, index) => (
|
45
|
-
<OverflowMenuItem key={props.getKey(item, index)} {...props} item={item} index={index} />
|
46
|
-
))}
|
47
|
-
</MenuList>
|
48
|
-
</MenuPopover>
|
49
|
-
</Menu>;
|
50
|
-
|
51
|
-
return (
|
52
|
-
props.menuWrapper
|
53
|
-
? props.menuWrapper(menu)
|
54
|
-
: menu
|
55
|
-
);
|
56
|
-
}
|
57
|
-
|
58
|
-
const OverflowMenuItem = <ItemType,>(props: IProps<ItemType> & { item: ItemType, index: number }) => {
|
59
|
-
const isVisible = useIsOverflowItemVisible(props.getKey(props.item, props.index));
|
60
|
-
|
61
|
-
if (isVisible) {
|
62
|
-
return null;
|
63
|
-
}
|
64
|
-
|
65
|
-
return (
|
66
|
-
<MenuItem key={props.getKey(props.item, props.index)}>
|
67
|
-
{props.renderItem(props.item, props.index, true)}
|
68
|
-
</MenuItem>
|
69
|
-
);
|
70
|
-
};
|
71
|
-
export const KWIZOverflow = <ItemType,>(props: IProps<ItemType>) => {
|
72
|
-
let content: JSX.Element[] = [];
|
73
|
-
let addMenu = () => {
|
74
|
-
if (menuIndex >= 0)
|
75
|
-
content.splice(menuIndex, 0, <OverflowMenu key="overflow_menu" {...props} />);
|
76
|
-
else
|
77
|
-
content.push(<OverflowMenu key="overflow_menu" {...props} />);
|
78
|
-
};
|
79
|
-
|
80
|
-
let menuIndex = -1;
|
81
|
-
|
82
|
-
props.items.forEach((item, index) => {
|
83
|
-
//add the menu before the first item with priority
|
84
|
-
let priority = props.getPriority ? props.getPriority(item, index) : undefined;
|
85
|
-
if (isNumber(priority) && priority > 0)
|
86
|
-
menuIndex = index;
|
87
|
-
|
88
|
-
content.push(<OverflowItem key={props.getKey(item, index)} id={props.getKey(item, index)}
|
89
|
-
priority={priority}>
|
90
|
-
{props.renderItem(item, index)}
|
91
|
-
</OverflowItem>);
|
92
|
-
});
|
93
|
-
|
94
|
-
addMenu();
|
95
|
-
|
96
|
-
return (
|
97
|
-
<Overflow minimumVisible={2} padding={60} key={`overflow_${props.items.length}`}>
|
98
|
-
<div style={{ overflow: "hidden" }} className={props.className}>
|
99
|
-
{
|
100
|
-
props.groupWrapper
|
101
|
-
? props.groupWrapper(content)
|
102
|
-
: content
|
103
|
-
}
|
104
|
-
</div>
|
105
|
-
</Overflow>
|
106
|
-
)
|
1
|
+
import {
|
2
|
+
Menu, MenuButton, MenuItem, MenuList, MenuPopover, MenuTrigger, Overflow, OverflowItem,
|
3
|
+
useIsOverflowItemVisible, useOverflowMenu
|
4
|
+
} from "@fluentui/react-components";
|
5
|
+
import { MoreHorizontalFilled } from "@fluentui/react-icons";
|
6
|
+
import { isNumber } from '@kwiz/common';
|
7
|
+
import { useKWIZFluentContext } from "../helpers/context-internal";
|
8
|
+
|
9
|
+
interface IProps<ItemType> {
|
10
|
+
/** you cannot have a menu with trigger in overflow items. put those in groupWrapper controls before/after rendering children. */
|
11
|
+
items: ItemType[];
|
12
|
+
getKey: (item: ItemType, index: number) => string;
|
13
|
+
getPriority?: (item: ItemType, index: number) => number;
|
14
|
+
renderItem: (item: ItemType, index: number, overflow?: boolean) => JSX.Element;
|
15
|
+
groupWrapper?: (children: React.ReactNode) => JSX.Element;
|
16
|
+
menuRef?: React.RefObject<HTMLButtonElement>;
|
17
|
+
menuWrapper?: (children: React.ReactNode) => JSX.Element;
|
18
|
+
menuTrigger?: (ref: React.RefObject<HTMLButtonElement>, overflowCount: number) => JSX.Element;
|
19
|
+
className?: string;
|
20
|
+
}
|
21
|
+
const OverflowMenu = <ItemType,>(props: IProps<ItemType>) => {
|
22
|
+
const ctx = useKWIZFluentContext();
|
23
|
+
|
24
|
+
const { ref, isOverflowing, overflowCount } =
|
25
|
+
useOverflowMenu<HTMLButtonElement>();
|
26
|
+
|
27
|
+
if (!isOverflowing) {
|
28
|
+
return null;
|
29
|
+
}
|
30
|
+
|
31
|
+
let menu = <Menu mountNode={ctx.mountNode}>
|
32
|
+
<MenuTrigger disableButtonEnhancement>
|
33
|
+
{props.menuTrigger
|
34
|
+
? props.menuTrigger(props.menuRef || ref, overflowCount)
|
35
|
+
: <MenuButton
|
36
|
+
icon={<MoreHorizontalFilled />}
|
37
|
+
ref={props.menuRef || ref}
|
38
|
+
aria-label="More items"
|
39
|
+
appearance="subtle"
|
40
|
+
/>}
|
41
|
+
</MenuTrigger>
|
42
|
+
<MenuPopover>
|
43
|
+
<MenuList>
|
44
|
+
{props.items.map((item, index) => (
|
45
|
+
<OverflowMenuItem key={props.getKey(item, index)} {...props} item={item} index={index} />
|
46
|
+
))}
|
47
|
+
</MenuList>
|
48
|
+
</MenuPopover>
|
49
|
+
</Menu>;
|
50
|
+
|
51
|
+
return (
|
52
|
+
props.menuWrapper
|
53
|
+
? props.menuWrapper(menu)
|
54
|
+
: menu
|
55
|
+
);
|
56
|
+
}
|
57
|
+
|
58
|
+
const OverflowMenuItem = <ItemType,>(props: IProps<ItemType> & { item: ItemType, index: number }) => {
|
59
|
+
const isVisible = useIsOverflowItemVisible(props.getKey(props.item, props.index));
|
60
|
+
|
61
|
+
if (isVisible) {
|
62
|
+
return null;
|
63
|
+
}
|
64
|
+
|
65
|
+
return (
|
66
|
+
<MenuItem key={props.getKey(props.item, props.index)}>
|
67
|
+
{props.renderItem(props.item, props.index, true)}
|
68
|
+
</MenuItem>
|
69
|
+
);
|
70
|
+
};
|
71
|
+
export const KWIZOverflow = <ItemType,>(props: IProps<ItemType>) => {
|
72
|
+
let content: JSX.Element[] = [];
|
73
|
+
let addMenu = () => {
|
74
|
+
if (menuIndex >= 0)
|
75
|
+
content.splice(menuIndex, 0, <OverflowMenu key="overflow_menu" {...props} />);
|
76
|
+
else
|
77
|
+
content.push(<OverflowMenu key="overflow_menu" {...props} />);
|
78
|
+
};
|
79
|
+
|
80
|
+
let menuIndex = -1;
|
81
|
+
|
82
|
+
props.items.forEach((item, index) => {
|
83
|
+
//add the menu before the first item with priority
|
84
|
+
let priority = props.getPriority ? props.getPriority(item, index) : undefined;
|
85
|
+
if (isNumber(priority) && priority > 0)
|
86
|
+
menuIndex = index;
|
87
|
+
|
88
|
+
content.push(<OverflowItem key={props.getKey(item, index)} id={props.getKey(item, index)}
|
89
|
+
priority={priority}>
|
90
|
+
{props.renderItem(item, index)}
|
91
|
+
</OverflowItem>);
|
92
|
+
});
|
93
|
+
|
94
|
+
addMenu();
|
95
|
+
|
96
|
+
return (
|
97
|
+
<Overflow minimumVisible={2} padding={60} key={`overflow_${props.items.length}`}>
|
98
|
+
<div style={{ overflow: "hidden" }} className={props.className}>
|
99
|
+
{
|
100
|
+
props.groupWrapper
|
101
|
+
? props.groupWrapper(content)
|
102
|
+
: content
|
103
|
+
}
|
104
|
+
</div>
|
105
|
+
</Overflow>
|
106
|
+
)
|
107
107
|
};
|