@kwiz/fluentui 1.0.60 → 1.0.62

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 (76) 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.js +2 -8
  29. package/dist/controls/search.js.map +1 -1
  30. package/dist/controls/section.d.ts +3 -2
  31. package/dist/controls/section.js +15 -5
  32. package/dist/controls/section.js.map +1 -1
  33. package/dist/controls/svg.d.ts +3 -0
  34. package/dist/controls/svg.js +5 -0
  35. package/dist/controls/svg.js.map +1 -1
  36. package/dist/controls/toolbar.js +2 -3
  37. package/dist/controls/toolbar.js.map +1 -1
  38. package/dist/controls/vertical.d.ts +4 -2
  39. package/dist/controls/vertical.js +6 -1
  40. package/dist/controls/vertical.js.map +1 -1
  41. package/dist/helpers/context-const.d.ts +2 -0
  42. package/dist/helpers/context-const.js.map +1 -1
  43. package/dist/helpers/context-export.d.ts +9 -1
  44. package/dist/helpers/context-export.js +43 -6
  45. package/dist/helpers/context-export.js.map +1 -1
  46. package/dist/helpers/forwardRef.d.ts +4 -0
  47. package/dist/helpers/forwardRef.js +2 -0
  48. package/dist/helpers/forwardRef.js.map +1 -0
  49. package/dist/helpers/hooks-events.d.ts +3 -3
  50. package/dist/helpers/hooks-events.js.map +1 -1
  51. package/dist/helpers/hooks.d.ts +1 -1
  52. package/dist/helpers/hooks.js +15 -6
  53. package/dist/helpers/hooks.js.map +1 -1
  54. package/dist/styles/styles.d.ts +5 -1
  55. package/dist/styles/styles.js +5 -18
  56. package/dist/styles/styles.js.map +1 -1
  57. package/package.json +3 -1
  58. package/src/controls/button.tsx +2 -3
  59. package/src/controls/centered.tsx +2 -10
  60. package/src/controls/date.tsx +1 -1
  61. package/src/controls/dropdown.tsx +37 -36
  62. package/src/controls/index.ts +2 -0
  63. package/src/controls/input.tsx +1 -1
  64. package/src/controls/list.tsx +4 -2
  65. package/src/controls/merge-text.tsx +126 -0
  66. package/src/controls/search.tsx +0 -4
  67. package/src/controls/section.tsx +17 -5
  68. package/src/controls/svg.tsx +8 -0
  69. package/src/controls/toolbar.tsx +2 -4
  70. package/src/controls/vertical.tsx +9 -1
  71. package/src/helpers/context-const.ts +2 -0
  72. package/src/helpers/context-export.tsx +51 -7
  73. package/src/helpers/forwardRef.ts +7 -0
  74. package/src/helpers/hooks-events.ts +2 -2
  75. package/src/helpers/hooks.tsx +16 -7
  76. package/src/styles/styles.ts +5 -18
@@ -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
+ }
@@ -11,12 +11,8 @@ const useStyles = makeStyles({
11
11
  main: mixins.main,
12
12
  clickable: mixins.clickable,
13
13
  root: {
14
- paddingLeft: 0
15
14
  },
16
15
  searchIcon: {
17
- position: "absolute",
18
- left: '5px',
19
- top: '5px'
20
16
  },
21
17
  })
22
18
 
@@ -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
@@ -1,31 +1,75 @@
1
- import { makeStyles } from "@fluentui/react-components";
1
+ import { makeStaticStyles, makeStyles } from "@fluentui/react-components";
2
2
  import React, { PropsWithChildren, useEffect, useState } from "react";
3
+ import { GetLogger } from "../_modules/config";
4
+ import { KnownClassNames } from "../styles";
3
5
  import { iKWIZFluentContext, KWIZFluentContext } from "./context-const";
6
+ import { DragDropContextProvider } from "./drag-drop";
4
7
  export type { iKWIZFluentContext } from "./context-const";
5
-
8
+ const logger = GetLogger("KWIZFluentContextProvider");
6
9
  const useContextStyles = makeStyles({
7
10
  root: {
8
11
  "& *": {
9
12
  scrollbarWidth: "thin"
10
- }
13
+ },
14
+ [`& .${KnownClassNames.printShow}`]: {
15
+ '@media print': {
16
+ display: 'unset',
17
+ }
18
+ },
19
+ [`& .${KnownClassNames.printHide}`]: {
20
+ '@media print': {
21
+ display: 'none !important'
22
+ }
23
+ },
24
+ },
25
+ });
26
+ export const useStaticStyles = makeStaticStyles({
27
+ [`.${KnownClassNames.printShow}`]: {
28
+ display: 'none'
11
29
  },
12
- })
30
+ [`body.${KnownClassNames.print} .${KnownClassNames.printHide}`]: {
31
+ display: "none !important"
32
+ },
33
+ [`body.${KnownClassNames.print} .${KnownClassNames.printShow}`]: {
34
+ display: "unset"
35
+ }
36
+ });
13
37
 
38
+ /** @deprecated - use KWIZFluentProvider instead of using this + DragDropContextProvider */
14
39
  export function useKWIZFluentContextProvider(options: {
15
40
  root?: React.MutableRefObject<HTMLDivElement>;
16
41
  ctx?: iKWIZFluentContext;
17
42
  }) {
43
+ useStaticStyles();
18
44
  const classes = useContextStyles();
19
45
  let v: iKWIZFluentContext = options && options.ctx || {};
20
46
  const [kwizFluentContext, setKwizFluentContext] = useState<iKWIZFluentContext>(v);
21
47
  useEffect(() => {
22
- options.root?.current?.classList.add(...classes.root.split(' '));
48
+ if (options.root?.current) logger.warn('Sending a root node is not recommended, if you have set up your packages correctly to mark react and fluent UI as external dialogs should open correctly.');
49
+ let styleRoot = options.root?.current || document.body;
50
+ styleRoot.classList.add(...classes.root.split(' '));
23
51
  // ref only updates in useEffect, not in useMemo or anything else.
24
52
  // we need to set it into state so it will trigger a ui update
25
53
  setKwizFluentContext({
26
54
  ...v,
27
- mountNode: options.root.current
55
+ mountNode: options.root?.current
28
56
  });
29
57
  }, [options.root]);
30
- return (props: PropsWithChildren) => <KWIZFluentContext.Provider value={kwizFluentContext}>{props.children}</KWIZFluentContext.Provider>;
58
+ return {
59
+ KWIZFluentContext,
60
+ value: kwizFluentContext
61
+ };
62
+ }
63
+
64
+ export const KWIZFluentProvider = (props: PropsWithChildren<{
65
+ ctx?: iKWIZFluentContext;
66
+ }>) => {
67
+
68
+ const cp = useKWIZFluentContextProvider({ ctx: props.ctx });
69
+
70
+ return <cp.KWIZFluentContext.Provider value={cp.value}>
71
+ <DragDropContextProvider>
72
+ {props.children}
73
+ </DragDropContextProvider>
74
+ </cp.KWIZFluentContext.Provider>;
31
75
  }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ //need to redeclare to support forward ref with generic types
3
+ declare module "react" {
4
+ function forwardRef<T, P = {}>(
5
+ render: (props: P, ref: React.Ref<T>) => React.JSX.Element | null
6
+ ): (props: P & React.RefAttributes<T>) => React.JSX.Element | null;
7
+ }
@@ -3,8 +3,8 @@ import { MutableRefObject, useEffect, useRef, useState } from "react";
3
3
  import { KnownClassNames } from "../styles/styles";
4
4
  import { useEffectOnlyOnMount } from "./hooks";
5
5
 
6
- export function useTrackFocus(props: { onFocus: () => void, onLoseFocus: () => void, ref?: MutableRefObject<HTMLElement> }) {
7
- const wrapperDiv = props.ref || useRef<HTMLDivElement>(null);
6
+ export function useTrackFocus<elmType extends HTMLElement>(props: { onFocus: () => void, onLoseFocus: () => void, ref?: MutableRefObject<elmType> }) {
7
+ const wrapperDiv: MutableRefObject<elmType> = props.ref || useRef<HTMLDivElement>(null) as any;
8
8
  useEffect(() => {
9
9
  function focusIn(e: FocusEvent) {
10
10
  let elm = e.target as HTMLElement;//document.activeElement;
@@ -2,13 +2,12 @@ import { isFunction, isNotEmptyArray, isNullOrEmptyString, isPrimitiveValue, jso
2
2
  import { MutableRefObject, SetStateAction, useCallback, useEffect, useRef, useState } from "react";
3
3
  import { GetLogger } from "../_modules/config";
4
4
 
5
- const logger = GetLogger("helpers/hooks");
6
5
  /** Empty array ensures that effect is only run on mount */
7
6
  export const useEffectOnlyOnMount = [];
8
7
 
9
8
  /** set state on steroids. provide promise callback after render, onChange transformer and automatic skip-set when value not changed */
10
9
  export function useStateEX<ValueType>(initialValue: ValueType, options?: {
11
- onChange?: (newValue: SetStateAction<ValueType>) => SetStateAction<ValueType>;
10
+ onChange?: (newValue: SetStateAction<ValueType>, isValueChanged: boolean) => SetStateAction<ValueType>;
12
11
  //will not set state if value did not change
13
12
  skipUpdateIfSame?: boolean;
14
13
  //optional, provide a name for better logging
@@ -53,26 +52,36 @@ export function useStateEX<ValueType>(initialValue: ValueType, options?: {
53
52
  }
54
53
  }, [value, resolveState.current]);
55
54
 
56
- let setValueWithCheck = !options.skipUpdateIfSame ? setValueInState : (newValue: ValueType) => {
57
- logger.groupSync('conditional value change', log => {
55
+ function getIsValueChanged(newValue: ValueType): boolean {
56
+ return logger.groupSync('getIsValueChanged', log => {
58
57
  if (logger.getLevel() === LoggerLevel.VERBOSE) {
59
58
  log('old: ' + jsonStringify(currentValue.current));
60
59
  log('new: ' + jsonStringify(newValue));
61
60
  }
62
61
  if (!objectsEqual(newValue as object, currentValue.current as object)) {
63
62
  log(`value changed`);
64
- setValueInState(newValue);
63
+ return true;
65
64
  }
66
65
  else {
67
66
  log(`value unchanged`);
68
- resolvePromises();
67
+ return false;
69
68
  }
70
69
  });
70
+ };
71
+
72
+ let setValueWithCheck = !options.skipUpdateIfSame ? setValueInState : (newValue: ValueType) => {
73
+ const isValueChanged = getIsValueChanged(newValue);
74
+ if (isValueChanged) {
75
+ setValueInState(newValue);
76
+ }
77
+ else {
78
+ resolvePromises();
79
+ }
71
80
  }
72
81
 
73
82
 
74
83
  let setValueWithEvents = wrapFunction(setValueWithCheck, {
75
- before: (newValue: ValueType) => isFunction(options.onChange) ? options.onChange(newValue) : newValue,
84
+ before: (newValue: ValueType) => isFunction(options.onChange) ? options.onChange(newValue, getIsValueChanged(newValue)) : newValue,
76
85
  after: (newValue: ValueType) => currentValue.current = isPrimitiveValue(newValue) || isFunction(newValue)
77
86
  ? newValue
78
87
  //fix skipUpdateIfSame for complex objects
@@ -58,6 +58,8 @@ export module mixins {
58
58
 
59
59
  export const KnownClassNames = {
60
60
  print: 'print-root',
61
+ printHide: 'print-hide',
62
+ printShow: 'print-show',
61
63
  section: 'kfui-section',
62
64
  vertical: 'kfui-vertical',
63
65
  horizontal: 'kfui-horizontal',
@@ -67,26 +69,11 @@ export const KnownClassNames = {
67
69
  accordionBody: 'kfui-accordion-body',
68
70
  accordionBodyWrapper: 'kfui-accordion-body-wrapper',
69
71
  isOpen: 'is-opened',
70
- progressBarStepLabel: 'step-label'
72
+ progressBarStepLabel: 'step-label',
73
+ left: 'float-left',
74
+ right: 'float-right'
71
75
  }
72
76
  export const useCommonStyles = makeStyles({
73
- printShow: {
74
- display: 'none',
75
- [`:global(body.${KnownClassNames.print})`]: {
76
- display: 'unset',
77
- },
78
- '@media print': {
79
- display: 'unset',
80
- }
81
- },
82
- printHide: {
83
- [`:global(body.${KnownClassNames.print})`]: {
84
- display: 'none !important'
85
- },
86
- '@media print': {
87
- display: 'none !important'
88
- }
89
- },
90
77
  hintLabel: {
91
78
  color: tokens.colorNeutralForeground3,
92
79
  fontSize: tokens.fontSizeBase200,