@kwiz/fluentui 1.0.61 → 1.0.63

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 (77) hide show
  1. package/README.md +37 -11
  2. package/dist/controls/button.d.ts +11 -11
  3. package/dist/controls/button.js +2 -3
  4. package/dist/controls/button.js.map +1 -1
  5. package/dist/controls/centered.js +1 -8
  6. package/dist/controls/centered.js.map +1 -1
  7. package/dist/controls/date.js +1 -1
  8. package/dist/controls/date.js.map +1 -1
  9. package/dist/controls/diagram-picker.d.ts +2 -2
  10. package/dist/controls/divider.d.ts +2 -2
  11. package/dist/controls/dropdown.d.ts +4 -9
  12. package/dist/controls/dropdown.js +27 -28
  13. package/dist/controls/dropdown.js.map +1 -1
  14. package/dist/controls/file-upload.d.ts +1 -1
  15. package/dist/controls/horizontal.d.ts +2 -2
  16. package/dist/controls/index.d.ts +1 -0
  17. package/dist/controls/index.js +1 -0
  18. package/dist/controls/index.js.map +1 -1
  19. package/dist/controls/input.js +1 -1
  20. package/dist/controls/input.js.map +1 -1
  21. package/dist/controls/list.js +4 -1
  22. package/dist/controls/list.js.map +1 -1
  23. package/dist/controls/merge-text.d.ts +16 -0
  24. package/dist/controls/merge-text.js +81 -0
  25. package/dist/controls/merge-text.js.map +1 -0
  26. package/dist/controls/progress-bar.d.ts +2 -2
  27. package/dist/controls/prompt.d.ts +1 -1
  28. package/dist/controls/search.d.ts +3 -2
  29. package/dist/controls/search.js +20 -34
  30. package/dist/controls/search.js.map +1 -1
  31. package/dist/controls/section.d.ts +3 -2
  32. package/dist/controls/section.js +15 -5
  33. package/dist/controls/section.js.map +1 -1
  34. package/dist/controls/svg.d.ts +3 -0
  35. package/dist/controls/svg.js +5 -0
  36. package/dist/controls/svg.js.map +1 -1
  37. package/dist/controls/toolbar.js +2 -3
  38. package/dist/controls/toolbar.js.map +1 -1
  39. package/dist/controls/vertical.d.ts +4 -2
  40. package/dist/controls/vertical.js +6 -1
  41. package/dist/controls/vertical.js.map +1 -1
  42. package/dist/helpers/context-const.d.ts +2 -0
  43. package/dist/helpers/context-const.js.map +1 -1
  44. package/dist/helpers/context-export.d.ts +6 -1
  45. package/dist/helpers/context-export.js +44 -6
  46. package/dist/helpers/context-export.js.map +1 -1
  47. package/dist/helpers/forwardRef.d.ts +4 -0
  48. package/dist/helpers/forwardRef.js +2 -0
  49. package/dist/helpers/forwardRef.js.map +1 -0
  50. package/dist/helpers/hooks-events.d.ts +3 -3
  51. package/dist/helpers/hooks-events.js.map +1 -1
  52. package/dist/helpers/hooks.d.ts +1 -1
  53. package/dist/helpers/hooks.js +15 -6
  54. package/dist/helpers/hooks.js.map +1 -1
  55. package/dist/styles/styles.d.ts +5 -1
  56. package/dist/styles/styles.js +5 -18
  57. package/dist/styles/styles.js.map +1 -1
  58. package/package.json +3 -1
  59. package/src/controls/button.tsx +2 -3
  60. package/src/controls/centered.tsx +2 -10
  61. package/src/controls/date.tsx +1 -1
  62. package/src/controls/dropdown.tsx +37 -36
  63. package/src/controls/index.ts +2 -0
  64. package/src/controls/input.tsx +1 -1
  65. package/src/controls/list.tsx +4 -2
  66. package/src/controls/merge-text.tsx +126 -0
  67. package/src/controls/search.tsx +20 -33
  68. package/src/controls/section.tsx +17 -5
  69. package/src/controls/svg.tsx +8 -0
  70. package/src/controls/toolbar.tsx +2 -4
  71. package/src/controls/vertical.tsx +9 -1
  72. package/src/helpers/context-const.ts +2 -0
  73. package/src/helpers/context-export.tsx +52 -8
  74. package/src/helpers/forwardRef.ts +7 -0
  75. package/src/helpers/hooks-events.ts +2 -2
  76. package/src/helpers/hooks.tsx +16 -7
  77. package/src/styles/styles.ts +5 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kwiz/fluentui",
3
- "version": "1.0.61",
3
+ "version": "1.0.63",
4
4
  "description": "KWIZ common controls for FluentUI",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -61,6 +61,8 @@
61
61
  "@fluentui/react-datepicker-compat": "^0.4.53",
62
62
  "@fluentui/react-timepicker-compat": "^0.2.42",
63
63
  "@kwiz/common": "^1.0.103",
64
+ "@mismerge/core": "^1.2.1",
65
+ "@mismerge/react": "^1.0.1",
64
66
  "esbuild": "^0.19.12",
65
67
  "get-tsconfig": "^4.7.2",
66
68
  "jodit": "^4.2.47",
@@ -2,7 +2,7 @@ import { Button, ButtonProps, CompoundButton, compoundButtonClassNames, Compound
2
2
  import { capitalizeFirstLetter, isFunction, isNullOrEmptyString, isNullOrUndefined, isString, PushNoDuplicate } from '@kwiz/common';
3
3
  import React from 'react';
4
4
  import { useKWIZFluentContext } from '../helpers/context-internal';
5
- import { commonSizes, useCommonStyles } from '../styles/styles';
5
+ import { commonSizes, KnownClassNames } from '../styles/styles';
6
6
 
7
7
  interface IProps {
8
8
  title: string;//required
@@ -72,11 +72,10 @@ export const ButtonEX = React.forwardRef<HTMLButtonElement, (ButtonEXProps)>((pr
72
72
  let hasIcon = !isNullOrUndefined(icon);
73
73
  let hasText = props.children || !hasIcon || (hasIcon && props.showTitleWithIcon === true);
74
74
 
75
- const commonCssNames = useCommonStyles();
76
75
  const cssNames = useStyles();
77
76
  let css: string[] = [];
78
77
 
79
- if (props.hideOnPrint) PushNoDuplicate(css, commonCssNames.printHide);
78
+ if (props.hideOnPrint) PushNoDuplicate(css, KnownClassNames.printHide);
80
79
  if (props.dontCenterText) PushNoDuplicate(css, cssNames.buttonNoCenter);
81
80
 
82
81
  let btn = <Button ref={ref} appearance='subtle' {...props as any as ButtonProps} className={mergeClasses(...css, props.className)}
@@ -1,21 +1,13 @@
1
- import { makeStyles } from '@fluentui/react-components';
2
1
  import React from 'react';
3
2
  import { Horizontal } from './horizontal';
4
3
  import { Vertical } from './vertical';
5
4
 
6
- const useStyles = makeStyles({
7
- center: {
8
- justifyContent: 'center'
9
- },
10
- })
11
-
12
5
  interface IProps {
13
6
  }
14
7
  export const Centered: React.FunctionComponent<React.PropsWithChildren<IProps>> = (props) => {
15
- const cssNames = useStyles();
16
8
  return (
17
- <Vertical main css={[cssNames.center]}>
18
- <Horizontal css={[cssNames.center]}>
9
+ <Vertical main vCentered>
10
+ <Horizontal hCentered>
19
11
  {props.children}
20
12
  </Horizontal>
21
13
  </Vertical>
@@ -40,7 +40,7 @@ export const DatePickerEx: React.FunctionComponent<React.PropsWithChildren<IProp
40
40
  timeValue ? timeValue.getMinutes() : 0, 0, 0
41
41
  );
42
42
  props.onDateChange(newDate);
43
- }, [timeValue]);
43
+ }, [timeValue, props.onDateChange]);
44
44
 
45
45
  const changeTimeHandler = React.useCallback((newTimeValue: Date): void => {
46
46
  //update our state
@@ -1,18 +1,20 @@
1
1
  import { Dropdown, DropdownProps, makeStyles, mergeClasses, Option } from '@fluentui/react-components';
2
2
  import { filterEmptyEntries, firstOrNull, isNullOrUndefined } from '@kwiz/common';
3
3
  import React from 'react';
4
+ import { GetLogger } from '../_modules/config';
4
5
  import { useKWIZFluentContext } from '../helpers/context-internal';
5
6
 
7
+ const logger = GetLogger("DropdownEX");
8
+
6
9
  const useStyles = makeStyles({
7
10
  root: {
8
11
  minWidth: "auto"
9
12
  },
10
13
  });
11
14
 
12
-
13
15
  type ForwardProps = Omit<DropdownProps, "onSelect" | "selectedOptions" | "clearable">;
14
16
 
15
- interface IProps<dataType, keyType extends string = string> extends ForwardProps {
17
+ interface IProps<keyType, dataType> extends ForwardProps {
16
18
  required?: boolean;
17
19
  selected: keyType | keyType[];
18
20
  items: {
@@ -27,40 +29,39 @@ interface IProps<dataType, keyType extends string = string> extends ForwardProps
27
29
  options?: { key: keyType, value: string, data?: dataType }[]) => void;
28
30
  }
29
31
 
30
- /** get a DropdownEX typed with forward ref. Usage:
31
- * const MyDropdownEX = DropdownEX3<myKeyType, myDataType>();
32
- * ...
33
- * <MyDropdownEX ... />
34
- */
35
- export function getDropdownEX<keyType extends string = string, dataType = never>() {
36
- return React.forwardRef<HTMLButtonElement, (IProps<dataType, keyType>)>((props, ref) => {
37
- const classes = useStyles();
38
- const ctx = useKWIZFluentContext();
39
- const selected: keyType[] = Array.isArray(props.selected) ? props.selected : isNullOrUndefined(props.selected) ? [] : [props.selected];
32
+ function $DropdownEX<keyType extends string = string, dataType = never>(props: IProps<keyType, dataType>, ref: React.ForwardedRef<HTMLButtonElement>) {
33
+ const classes = useStyles();
34
+ const ctx = useKWIZFluentContext();
35
+ const selected: keyType[] = Array.isArray(props.selected) ? props.selected : isNullOrUndefined(props.selected) ? [] : [props.selected];
40
36
 
41
- //sometimes control will lose value when re-rendered
42
- //use case: public forms when editing other fields after the dropdown was set
43
- //re-set the text value manually to fix
44
- let text = filterEmptyEntries((Array.isArray(props.selected) ? props.selected : [props.selected]).map(s => {
45
- let v = firstOrNull(props.items, i => i.key === s);
46
- return v ? v.value : ''
47
- })).join(', ');
37
+ //sometimes control will lose value when re-rendered
38
+ //use case: public forms when editing other fields after the dropdown was set
39
+ //re-set the text value manually to fix
40
+ let text = filterEmptyEntries((Array.isArray(props.selected) ? props.selected : [props.selected]).map(s => {
41
+ let v = firstOrNull(props.items, i => i.key === s);
42
+ return v ? v.value : ''
43
+ })).join(', ');
48
44
 
49
- return (
50
- <Dropdown {...{ ...props, onSelect: undefined }} className={mergeClasses(classes.root, props.className)} ref={ref} clearable={!props.required && !props.multiselect}
51
- appearance={ctx.inputAppearance} mountNode={ctx.mountNode}
52
- selectedOptions={selected} value={text} onOptionSelect={(e, data) => {
53
- let o = firstOrNull(props.items, i => i.key === data.optionValue);
54
- if (props.multiselect) {
55
- let current = data.selectedOptions.map(s => firstOrNull(props.items, i => i.key === s));
56
- props.onSelect(o, current);
57
- }
58
- else props.onSelect(o);
59
- }}>
60
- {props.items.map(i => <Option key={i.key} value={i.key} text={i.value}>{i.option ? i.option : i.value}</Option>)}
61
- </Dropdown>
62
- );
63
- });
45
+ return (
46
+ <Dropdown {...{ ...props, onSelect: undefined }} className={mergeClasses(classes.root, props.className)} ref={ref} clearable={!props.required && !props.multiselect}
47
+ appearance={ctx.inputAppearance} mountNode={ctx.mountNode}
48
+ selectedOptions={selected} value={text} onOptionSelect={(e, data) => {
49
+ let o = firstOrNull(props.items, i => i.key === data.optionValue);
50
+ if (props.multiselect) {
51
+ let current = data.selectedOptions.map(s => firstOrNull(props.items, i => i.key === s));
52
+ props.onSelect(o, current);
53
+ }
54
+ else props.onSelect(o);
55
+ }}>
56
+ {props.items.map(i => <Option key={i.key} value={i.key} text={i.value}>{i.option ? i.option : i.value}</Option>)}
57
+ </Dropdown>
58
+ );
64
59
  }
65
- /** to get typed keys use getDropdownEX */
66
- export const DropdownEX = getDropdownEX();
60
+
61
+ export const DropdownEX = React.forwardRef($DropdownEX);
62
+
63
+ /** @deprecated use normal DropdownEX it is now generic */
64
+ export function getDropdownEX<keyType extends string = string, dataType = never>() {
65
+ logger.warn('getDropdownEX is deprecated. use DropdownEX it now supports generic types');
66
+ return React.forwardRef($DropdownEX<keyType, dataType>);
67
+ }
@@ -19,6 +19,7 @@ export * from './kwizoverflow';
19
19
  export * from './list';
20
20
  export * from './loading';
21
21
  export * from './menu';
22
+ export * from './merge-text';
22
23
  export * from './please-wait';
23
24
  export * from './progress-bar';
24
25
  export * from './prompt';
@@ -29,3 +30,4 @@ export * from './svg';
29
30
  export * from './toolbar';
30
31
  export * from './vertical';
31
32
  export * from './vertical-content';
33
+
@@ -112,7 +112,7 @@ export const InputNumberEx: React.FunctionComponent<React.PropsWithChildren<INum
112
112
  const isValid = props.required ? !isNullOrNaN(asNumber) : isNullOrUndefined(asNumber) || !isNaN(asNumber);
113
113
  setIsValid(isValid);
114
114
  props.onChange(isValid ? asNumber : null);
115
- }, [props.allowDecimals]);
115
+ }, [props.allowDecimals, props.onChange, props.required]);
116
116
 
117
117
  const passProps: IProps = { ...props, defaultValue: undefined, value: undefined, onChange: undefined };
118
118
 
@@ -1,6 +1,7 @@
1
1
  import { makeStyles, tokens } from '@fluentui/react-components';
2
2
  import { LOGO_BLUE_SQUARE, LOGO_WHITE_SQUARE, isNullOrUndefined, isString } from '@kwiz/common';
3
3
  import React from 'react';
4
+ import { useKWIZFluentContext } from '../helpers/context-internal';
4
5
  import { KnownClassNames, mixins } from '../styles/styles';
5
6
  import { Horizontal } from './horizontal';
6
7
  import { Section } from './section';
@@ -85,8 +86,9 @@ interface IProps {
85
86
  }
86
87
 
87
88
  export const ListEx = (props: IProps) => {
88
-
89
+ const ctx = useKWIZFluentContext();
89
90
  const cssNames = useStyles();
91
+ const isDark = ctx.dark === true || props.dark === true;
90
92
 
91
93
  const listItemElm = (item: iListItem) => <Horizontal key={item.key} css={[cssNames.listItem, item.selected && cssNames.listItemSelected]} onClick={item.onClick}>
92
94
  {item.media && <Section css={[cssNames.media]} onClick={(e) => {
@@ -94,7 +96,7 @@ export const ListEx = (props: IProps) => {
94
96
  e.stopPropagation();//media may have its on onclick
95
97
  }}>{
96
98
  isString(item.media)
97
- ? <div className={cssNames.image} style={{ backgroundImage: `url('${encodeURI(item.media)}'), url('${props.dark ? LOGO_WHITE_SQUARE : LOGO_BLUE_SQUARE}')` }}></div>
99
+ ? <div className={cssNames.image} style={{ backgroundImage: `url('${encodeURI(item.media)}'), url('${isDark ? LOGO_WHITE_SQUARE : LOGO_BLUE_SQUARE}')` }}></div>
98
100
  : item.media
99
101
  }</Section>}
100
102
  <Vertical main css={[cssNames.listItemBody]}>
@@ -0,0 +1,126 @@
1
+ import { Drawer, DrawerBody, DrawerHeader, DrawerHeaderTitle, Field, Label, makeStyles, Radio, tokens } from '@fluentui/react-components';
2
+ import { DismissRegular, SaveRegular } from '@fluentui/react-icons';
3
+ import { isNullOrUndefined, waitFor } from '@kwiz/common';
4
+ import { DefaultDarkColors, DefaultLightColors, MisMerge2 } from '@mismerge/react';
5
+ import * as React from 'react';
6
+
7
+ import '@mismerge/core/dark.css';
8
+ import '@mismerge/core/styles.css';
9
+ import { useStateEX, useWindowSize } from '../helpers';
10
+ import { useKWIZFluentContext } from '../helpers/context-internal';
11
+ import { ButtonEX, ButtonEXPrimarySubtle } from './button';
12
+ import { Horizontal } from './horizontal';
13
+ import { Section } from './section';
14
+ import { Vertical } from './vertical';
15
+
16
+ const useStyles = makeStyles({
17
+ root: {
18
+ // position: 'fixed',
19
+ // top: 0,
20
+ // left: 0,
21
+ // right: 0,
22
+ // bottom: 0,
23
+ // backgroundColor: tokens.colorNeutralBackground1,
24
+ // zIndex: 10,
25
+ "& .mismerge": {
26
+ // height: "100%",
27
+ "--background": tokens.colorNeutralBackground1,
28
+ //line number background
29
+ "--primary-100": tokens.colorNeutralBackground2,
30
+ //selection background
31
+ "--selection": tokens.colorNeutralBackground1Selected,
32
+ //scrollbar hover
33
+ "--primary-300": tokens.colorNeutralBackground1Hover,
34
+ //border / scroll
35
+ "--primary-200": tokens.colorNeutralStroke1,
36
+ //line number color
37
+ "--primary-400": tokens.colorNeutralForeground2,
38
+ //button hover color
39
+ "--primary-500": tokens.colorNeutralForeground1Hover,
40
+ //main text color
41
+ "--primary-600": tokens.colorNeutralForeground1,
42
+ "& TEXTAREA": {
43
+ lineHeight: "20px"
44
+ }
45
+ }
46
+ },
47
+ menu: {
48
+ justifyContent: "space-between"
49
+ }
50
+ });
51
+
52
+ interface IProps {
53
+ title: string;
54
+ description?: string;
55
+ lhsTitle: string;
56
+ lhsValue: string;
57
+ rhsTitle: string;
58
+ rhsValue: string;
59
+ dark?: boolean;
60
+ save: (merged: string) => void;
61
+ cancel: () => void;
62
+ }
63
+ export const MergeText: React.FunctionComponent<React.PropsWithChildren<IProps>> = (props) => {
64
+ const classes = useStyles();
65
+ const ctx = useKWIZFluentContext();
66
+
67
+ let size = useWindowSize();
68
+ let wrapper = React.useRef<HTMLDivElement>();
69
+ let [lhs, setLhs] = useStateEX(props.lhsValue || "", {
70
+ skipUpdateIfSame: true, onChange: (v, changed) => {
71
+ if (changed) setKeep("left"); return v;
72
+ }
73
+ });
74
+ let [rhs, setRhs] = useStateEX(props.rhsValue || "", {
75
+ skipUpdateIfSame: true, onChange: (v, changed) => {
76
+ if (changed) setKeep("right"); return v;
77
+ }
78
+ });
79
+ let [keep, setKeep] = useStateEX<"cancel" | "left" | "right">("cancel");
80
+
81
+ React.useEffect(() => {
82
+ if (wrapper.current) {
83
+ waitFor(() => !isNullOrUndefined(wrapper.current.querySelector(".mismerge"))).then(() => {
84
+ let mismerge = wrapper.current.querySelector(".mismerge") as HTMLDivElement;
85
+ if (mismerge)
86
+ mismerge.style.height = `${mismerge.offsetParent.clientHeight - mismerge.offsetTop - 10}px`;
87
+ });
88
+ }
89
+ }, [wrapper.current, size.height]);
90
+
91
+ return <Drawer type='overlay' open size='full' className={classes.root} mountNode={ctx.mountNode}>
92
+ <DrawerHeader>
93
+ <DrawerHeaderTitle action={<ButtonEX icon={<DismissRegular />} title="Close" onClick={props.cancel} />}>
94
+ {props.title}
95
+ </DrawerHeaderTitle>
96
+ </DrawerHeader>
97
+ <DrawerBody>
98
+ <Vertical>
99
+ {props.description && <Label>{props.description}</Label>}
100
+ <Field label="Which version would you like to keep?"
101
+ hint="Merge the changes to either side and save. Close this panel to keep editing the page without saving">
102
+ <Horizontal css={[classes.menu]}>
103
+ <Horizontal nogap>
104
+ <Radio value="left" label={props.lhsTitle} checked={keep === "left"} onClick={() => setKeep("left")} />
105
+ <ButtonEXPrimarySubtle showTitleWithIcon dontCenterText icon={<SaveRegular />} disabled={keep !== "left"} title={`Save ${props.lhsTitle.toLowerCase()}`} onClick={() => props.save(lhs)} />
106
+ </Horizontal>
107
+ <Horizontal nogap>
108
+ <Radio value="right" label={props.rhsTitle} checked={keep === "right"} onClick={() => setKeep("right")} />
109
+ <ButtonEXPrimarySubtle showTitleWithIcon dontCenterText icon={<SaveRegular />} disabled={keep !== "right"} title={`Save ${props.rhsTitle.toLowerCase()}`} onClick={() => props.save(rhs)} />
110
+ </Horizontal>
111
+ </Horizontal>
112
+ </Field>
113
+ <Section main ref={wrapper}>
114
+ <MisMerge2
115
+ lhs={lhs}
116
+ rhs={rhs}
117
+ lhsEditable rhsEditable
118
+ onLhsChange={v => setLhs(v)}
119
+ onRhsChange={v => setRhs(v)}
120
+ colors={props.dark ? DefaultDarkColors : DefaultLightColors}
121
+ />
122
+ </Section>
123
+ </Vertical>
124
+ </DrawerBody>
125
+ </Drawer>;
126
+ }
@@ -1,9 +1,8 @@
1
1
  import { Input, InputProps, makeStyles, mergeClasses } from '@fluentui/react-components';
2
2
  import { DismissRegular, SearchRegular } from "@fluentui/react-icons";
3
- import { debounce, isFunction, isNullOrEmptyString, isUndefined } from '@kwiz/common';
4
- import React, { useState } from 'react';
3
+ import { debounce, isNullOrEmptyString } from '@kwiz/common';
4
+ import React, { useRef } from 'react';
5
5
  import { GetLogger } from '../_modules/config';
6
- import { useStateEX } from '../helpers';
7
6
  import { mixins } from '../styles/styles';
8
7
  const logger = GetLogger("Search");
9
8
 
@@ -11,64 +10,52 @@ const useStyles = makeStyles({
11
10
  main: mixins.main,
12
11
  clickable: mixins.clickable,
13
12
  root: {
14
- paddingLeft: 0
15
13
  },
16
14
  searchIcon: {
17
- position: "absolute",
18
- left: '5px',
19
- top: '5px'
20
15
  },
21
16
  })
22
17
 
23
- interface IProps extends InputProps {
18
+ interface IProps extends Omit<InputProps, "onChange"> {
24
19
  main?: boolean;
20
+ /** number of seconds to debounce the deferred event */
25
21
  delay?: number;
26
22
  /** if changing the value in the caller - change this prop to reset */
27
23
  resetValue?: string;
28
24
  onChangeDeferred?: (newValue: string) => void;
29
- onChangeSync?: (newValue: string) => void;
25
+ onChange?: (newValue: string) => void;
30
26
  }
31
27
 
32
28
  /** value is set on first load. to change the value after it was first set - change the compoenet's key. */
33
29
  export const Search: React.FunctionComponent<React.PropsWithChildren<IProps>> = (props) => {
34
30
  const cssNames = useStyles();
35
31
 
36
- const [resetKey, setResetKey] = useState(1);
37
-
38
32
  let delay = props.delay || 1;
39
33
 
34
+ let refonChangeDeferred = useRef(props.onChangeDeferred);
35
+ //keep updating the ref
36
+ React.useEffect(() => { refonChangeDeferred.current = props.onChangeDeferred; }, [props.onChangeDeferred]);
37
+
40
38
  //cannot call debounce every render, since it won't be the same debounced instance...
41
39
  var notifyParent = React.useMemo(() => debounce(v => {
42
40
  logger.log(`Set: ${v}`);
43
- props.onChangeDeferred(v);
41
+ //Call the latest ref - we don't want to call an old version of this function
42
+ refonChangeDeferred.current?.(v);
44
43
  }, delay * 1000), [delay]);
45
44
 
46
- let [value, setValue] = useStateEX(props.value || "", {
47
- onChange: newValue => {
48
- if (isFunction(props.onChangeSync)) props.onChangeSync(newValue as string);
49
- if (isFunction(props.onChangeDeferred)) notifyParent(newValue);
50
- return newValue;
51
- }
52
- });
53
-
54
- //once props change, reset this control value to match
55
- React.useEffect(() => {
56
- if (!isUndefined(props.resetValue))
57
- setValue(props.resetValue);
58
- //todo: bug: setting value does not sync into the text box
59
- setResetKey(resetKey + 1)
60
- }, [props.resetValue]);
45
+ const currentValue = props.value || "";
61
46
 
62
47
  return (
63
- <Input key={resetKey} {...props} value={value} onChange={(e, data) => setValue(data.value)}
48
+ <Input {...props} value={currentValue} onChange={(e, data) => {
49
+ props.onChange?.(data.value);
50
+ notifyParent(data.value);
51
+ }}
64
52
  className={mergeClasses(cssNames.root, props.main && cssNames.main)}
65
- contentBefore={!isNullOrEmptyString(value) ? undefined : <SearchRegular className={cssNames.searchIcon} />}
66
- contentAfter={isNullOrEmptyString(value)
53
+ contentBefore={!isNullOrEmptyString(currentValue) ? undefined : <SearchRegular className={cssNames.searchIcon} />}
54
+ contentAfter={isNullOrEmptyString(currentValue)
67
55
  ? undefined
68
56
  : <DismissRegular className={cssNames.clickable} onClick={() => {
69
- setValue("");
70
- //todo: bug: setting value does not sync into the text box
71
- setResetKey(resetKey + 1)
57
+ props.onChange?.("");
58
+ notifyParent("");
72
59
  }} />
73
60
  } />
74
61
  );
@@ -1,5 +1,5 @@
1
1
  import { makeStyles, mergeClasses, Portal, tokens } from '@fluentui/react-components';
2
- import { isFunction, isNotEmptyArray } from '@kwiz/common';
2
+ import { isFunction, isNotEmptyArray, isNotEmptyString } from '@kwiz/common';
3
3
  import React from 'react';
4
4
  import { useKWIZFluentContext } from '../helpers/context-internal';
5
5
  import { KnownClassNames, mixins, useCommonStyles } from '../styles/styles';
@@ -15,7 +15,10 @@ const useStyles = makeStyles({
15
15
  right: {
16
16
  ...mixins.float,
17
17
  float: "right",
18
- marginRight: tokens.spacingHorizontalXXL
18
+ marginLeft: tokens.spacingHorizontalXXL
19
+ },
20
+ selfCentered: {
21
+ alignSelf: "center"
19
22
  }
20
23
  });
21
24
 
@@ -31,6 +34,7 @@ export interface ISectionProps {
31
34
  right?: boolean;
32
35
  /** true - will add css position fixed. portal will also wrap it in a portal. */
33
36
  fullscreen?: boolean | "portal";
37
+ centerSelf?: boolean;
34
38
  }
35
39
 
36
40
  export const Section = React.forwardRef<HTMLDivElement, React.PropsWithChildren<ISectionProps>>((props, ref) => {
@@ -39,13 +43,21 @@ export const Section = React.forwardRef<HTMLDivElement, React.PropsWithChildren<
39
43
  const cssNames = useStyles();
40
44
  let css: string[] = [KnownClassNames.section];
41
45
  if (props.main) css.push(cssNames.main);
46
+ if (props.centerSelf) css.push(cssNames.selfCentered);
42
47
  if (isFunction(props.onClick))
43
48
  css.push(cssNames.clickable);
44
49
 
45
- if (props.left) css.push(cssNames.left);
46
- else if (props.right) css.push(cssNames.right);
50
+ if (props.left) {
51
+ css.push(cssNames.left);
52
+ css.push(KnownClassNames.left);
53
+ }
54
+ else if (props.right) {
55
+ css.push(cssNames.right);
56
+ css.push(KnownClassNames.right);
57
+ }
47
58
 
48
- if (isNotEmptyArray(props.css)) css.push(...props.css);
59
+ //a css class might have space and multiuple classes in it
60
+ if (isNotEmptyArray(props.css)) props.css.filter(c => isNotEmptyString(c)).forEach(c => css.push(...c.split(" ")));
49
61
  if (props.fullscreen) css.push(commonStyles.fullscreen);
50
62
  const control = <div ref={ref} {...(props.rootProps || {})} title={props.title} style={props.style}
51
63
  className={mergeClasses(...css)}
@@ -121,6 +121,14 @@ export const GetSVGSplitIcon = (props: { size: number }) => {
121
121
  </svg>`;
122
122
  }
123
123
 
124
+ export function GetSVGCopyIcon(props: { size: number }) {
125
+ return (
126
+ `<svg fill="var(--colorNeutralForeground1)" width="${props.size}px" height="${props.size}px" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
127
+ <path d="M8 2a2 2 0 0 0-2 2v10c0 1.1.9 2 2 2h6a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8ZM7 4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V4ZM4 6a2 2 0 0 1 1-1.73V14.5A2.5 2.5 0 0 0 7.5 17h6.23A2 2 0 0 1 12 18H7.5A3.5 3.5 0 0 1 4 14.5V6Z"></path>
128
+ </svg>`
129
+ )
130
+ }
131
+
124
132
  export function IconToSVG(icon: JSX.Element) {
125
133
  const iconDiv = document.createElement('div');
126
134
  const root = createRoot(iconDiv);
@@ -1,6 +1,6 @@
1
1
  import { Toolbar, ToolbarDivider, ToolbarGroup } from '@fluentui/react-components';
2
2
  import React from 'react';
3
- import { useCommonStyles } from '../styles/styles';
3
+ import { KnownClassNames } from '../styles/styles';
4
4
  import { KWIZOverflow } from './kwizoverflow';
5
5
 
6
6
  interface IProps {
@@ -10,8 +10,6 @@ interface IProps {
10
10
  sideButtons?: JSX.Element;
11
11
  }
12
12
  export const ToolbarEX: React.FunctionComponent<IProps> = (props) => {
13
- const commonCssNames = useCommonStyles();
14
-
15
13
  let elements: { id: string, priority?: number, elm: JSX.Element, overflowElement?: JSX.Element }[] = [];
16
14
  props.buttonGroups.forEach((group, groupIndex) => {
17
15
  group.forEach((button, buttonIndex) => {
@@ -32,7 +30,7 @@ export const ToolbarEX: React.FunctionComponent<IProps> = (props) => {
32
30
  });
33
31
 
34
32
  return (
35
- <KWIZOverflow className={commonCssNames.printHide}
33
+ <KWIZOverflow className={KnownClassNames.printHide}
36
34
  items={elements}
37
35
  getKey={e => e.id}
38
36
  renderItem={(e, i, overflow) => overflow && e.overflowElement || e.elm}
@@ -10,12 +10,18 @@ const useStyles = makeStyles({
10
10
  flexDirection: 'column'
11
11
  },
12
12
  wrap: mixins.wrap,
13
- nogap: mixins.nogap
13
+ nogap: mixins.nogap,
14
+ vCentered: {
15
+ justifyContent: "center"
16
+ },
17
+
14
18
  })
15
19
 
16
20
  interface IProps extends ISectionProps {
17
21
  wrap?: boolean;
18
22
  nogap?: boolean;
23
+ /** vertical centered */
24
+ vCentered?: boolean;
19
25
  }
20
26
  export const Vertical = React.forwardRef<HTMLDivElement, React.PropsWithChildren<IProps>>((props, ref) => {
21
27
  const cssNames = useStyles();
@@ -26,6 +32,8 @@ export const Vertical = React.forwardRef<HTMLDivElement, React.PropsWithChildren
26
32
  css.push(cssNames.wrap);
27
33
  if (props.nogap)
28
34
  css.push(cssNames.nogap);
35
+ if (props.vCentered)
36
+ css.push(cssNames.vCentered);
29
37
 
30
38
  if (isNotEmptyArray(props.css)) css.push(...props.css);
31
39
 
@@ -22,6 +22,8 @@ export interface iKWIZFluentContext {
22
22
  * @default 'rounded'
23
23
  */
24
24
  buttonShape?: 'rounded' | 'circular' | 'square';
25
+ /** true if using dark theme */
26
+ dark?: boolean;
25
27
  }
26
28
 
27
29
  //create context