@kwiz/fluentui 1.0.61 → 1.0.62

Sign up to get free protection for your applications and to get access to all the features.
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 +6 -1
  44. package/dist/helpers/context-export.js +44 -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 +52 -8
  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";
2
- import React, { useEffect, useState } from "react";
1
+ import { makeStaticStyles, makeStyles } from "@fluentui/react-components";
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 { KWIZFluentContext, value: kwizFluentContext };
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,