Skip to content

Commit 1e99c27

Browse files
committed
added progress bar, fixed drawpad signature
1 parent a99f1a8 commit 1e99c27

File tree

7 files changed

+150
-19
lines changed

7 files changed

+150
-19
lines changed

src/controls/canvas/DrawPad.tsx

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,14 @@ export const DrawPadUserName = {
3838
get: () => { return _userName },
3939
set: (userName: string) => { _userName = userName }
4040
};
41+
42+
const fontName = "Dancing Script";
43+
let fontLoading: Promise<boolean> = null;
44+
let fontReady = false;
4145
export const DrawPad: React.FunctionComponent<iProps> = (props) => {
4246
const [LineColor, setLineColor] = useStateEX<string>(props.LineColor || tokens.colorBrandForeground1);
4347
const [manager, setmanager] = useStateEX<DrawPadManager>(null);
4448
const [canUndo, setcanUndo] = useStateEX<boolean>(false, { skipUpdateIfSame: true });
45-
const [loadedFontNames, setloadedFontNames] = useStateEX<string[]>([]);
4649
const [signed, setSigned] = useStateEX<boolean>(false);
4750
const [fullscreen, setFullscreen] = useStateEX<boolean>(false);
4851
const onChangeRef = React.useRef(props.OnChange);
@@ -63,9 +66,9 @@ export const DrawPad: React.FunctionComponent<iProps> = (props) => {
6366

6467
//load font for sign as text, if needed
6568
React.useEffect(() => {
66-
if (props.allowSigning && isNullOrEmptyArray(loadedFontNames)) {
69+
if (props.allowSigning && !fontLoading) {
6770
let DancingScriptFont = new FontFace(
68-
"Dancing Script",
71+
fontName,
6972
"url(https://fonts.gstatic.com/s/dancingscript/v25/If2RXTr6YS-zF4S-kcSWSVi_szLgiuE.woff2) format('woff2')",
7073
{
7174
style: "normal",
@@ -75,13 +78,14 @@ export const DrawPad: React.FunctionComponent<iProps> = (props) => {
7578
}
7679
);
7780

78-
DancingScriptFont.load().then(async loadedFont => {
81+
fontLoading = DancingScriptFont.load().then(async loadedFont => {
7982
document.fonts.add(loadedFont);
8083
await document.fonts.ready;
81-
setloadedFontNames(["Dancing Script"]);
84+
fontReady = true;
85+
return true;
8286
});
8387
}
84-
}, [props.allowSigning, loadedFontNames]);
88+
}, [props.allowSigning]);
8589

8690
//setup manager
8791
React.useEffect(() => {
@@ -145,7 +149,6 @@ export const DrawPad: React.FunctionComponent<iProps> = (props) => {
145149

146150
let height = canvas.clientHeight;
147151
let width = canvas.clientWidth;
148-
let fontName = loadedFontNames[0];
149152

150153
let ctx = canvas.getContext("2d");
151154
ctx.fillStyle = getCSSVariableValue(LineColor, canvasArea.current);
@@ -174,7 +177,7 @@ export const DrawPad: React.FunctionComponent<iProps> = (props) => {
174177
if (isNullOrUndefined(DrawPadUserName.get())) {
175178
//prompt user to type his name - then continue
176179
alerts.promptEX({
177-
mountNode: canvasContainerDiv.current,
180+
//mountNode: canvasContainerDiv.current, this lets other content on the form cover the dialog
178181
title: "Sign as name",
179182
children: <Field label="Signing as" hint="Please type in your name" required>
180183
<InputEx onChange={(e, data) => DrawPadUserName.set(data.value)} />
@@ -237,7 +240,7 @@ export const DrawPad: React.FunctionComponent<iProps> = (props) => {
237240
}} />
238241
{!signed
239242
&& !isNullOrEmptyString(props.allowSigning)
240-
&& !isNullOrEmptyArray(loadedFontNames)
243+
&& !isNullOrEmptyArray(fontReady)
241244
&& <ButtonEX
242245
style={{
243246
position: "absolute",
@@ -258,9 +261,10 @@ export const DrawPad: React.FunctionComponent<iProps> = (props) => {
258261
}
259262
</div>
260263
{!props.ReadOnly && !HideButtons && <Vertical nogap>
261-
{props.HideColorPicker || <ColorPickerEx mountNode={canvasContainerDiv.current} disabled={props.disabled} buttonOnly value={props.LineColor} onChange={newColor => {
262-
setLineColor(newColor);
263-
}} />}
264+
{props.HideColorPicker || <ColorPickerEx //mountNode={canvasContainerDiv.current} this lets other content on the form cover the dialog
265+
disabled={props.disabled} buttonOnly value={props.LineColor} onChange={newColor => {
266+
setLineColor(newColor);
267+
}} />}
264268
{props.HideClear || <ButtonEX disabled={props.disabled || isNullOrEmptyString(props.Value)} title="Clear" icon={<DismissRegular />} onClick={() => {
265269
//can call clear on the canvas, or can call the onchange which will cause a re-draw
266270
setSigned(false);

src/controls/date.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@ interface IProps {
1414
showTime?: boolean;
1515
datePickerProps?: DatePickerProps;
1616
timePickerProps?: TimePickerProps;
17+
/** don't allow to clear the value */
18+
required?: boolean;
1719
}
1820
export const DatePickerEx: React.FunctionComponent<React.PropsWithChildren<IProps>> = (props) => {
1921
const ctx = useKWIZFluentContext();
2022

2123
//time value will always have a value even when clearing the date
2224
const [timeValue, setTimeValue] = useStateEX<Date>(isDate(props.value) ? props.value : new Date());
2325
const { showClear, dateValue } = React.useMemo(() => {
24-
const showClear = isDate(props.value);
26+
const showClear = !props.required && isDate(props.value);
2527
const dateValue = props.value;
2628
return { showClear, dateValue };
2729
}, [props.value]);

src/controls/horizontal.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,19 @@ const useStyles = makeStyles({
1313
nogap: mixins.nogap,
1414
centered: {
1515
alignItems: "center"
16-
}
16+
},
17+
hCentered: {
18+
justifyContent: "center"
19+
},
1720
})
1821

1922
interface IProps extends ISectionProps {
2023
wrap?: boolean;
2124
nogap?: boolean;
25+
/** vertical align center */
2226
centered?: boolean;
27+
/** horizontal centered */
28+
hCentered?: boolean;
2329
}
2430
export const Horizontal = React.forwardRef<HTMLDivElement, React.PropsWithChildren<IProps>>((props, ref) => {
2531
const cssNames = useStyles();
@@ -32,6 +38,8 @@ export const Horizontal = React.forwardRef<HTMLDivElement, React.PropsWithChildr
3238
css.push(cssNames.nogap);
3339
if (props.centered)
3440
css.push(cssNames.centered);
41+
if (props.hCentered)
42+
css.push(cssNames.hCentered);
3543

3644
if (isNotEmptyArray(props.css)) css.push(...props.css);
3745

src/controls/html-editor/editor.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ type JoditExpanded = Jodit & {//& IViewBased<IViewOptions>
5252
setShowFullScreen: (value: boolean) => void;
5353
}
5454
};
55+
56+
const saveIcon = IconToSVG(<Save16Regular title='Save' />);
57+
const cancelIcon = IconToSVG(<Dismiss16Regular title='Cancel' />);
58+
const maxIcon = IconToSVG(<ArrowMaximize16Regular title='Maximize' />);
59+
const minIcon = IconToSVG(<ArrowMinimize16Regular title='Minimize' />);
5560
export const HtmlEditor: React.FunctionComponent<React.PropsWithChildren<IProps>> = (props) => {
5661
const classes = useStyles();
5762
const [active, setActive] = React.useState(false);
@@ -82,29 +87,29 @@ export const HtmlEditor: React.FunctionComponent<React.PropsWithChildren<IProps>
8287

8388

8489
Jodit.defaultOptions.controls.save = {
85-
template: () => IconToSVG(<Save16Regular title='Save' />),
90+
template: () => saveIcon,
8691
exec: (view: JoditExpanded) => {
8792
view.kwizInstance.props.onChange?.(view.value);
8893
view.kwizInstance.props.onSave?.(view.value);
8994
view.kwizInstance.setShowFullScreen(false);
9095
}
9196
};
9297
Jodit.defaultOptions.controls.cancel = {
93-
template: () => IconToSVG(<Dismiss16Regular title='Cancel' />),
98+
template: () => cancelIcon,
9499
exec: (view: JoditExpanded) => {
95100
view.kwizInstance.props.onCancel?.();
96101
view.kwizInstance.setShowFullScreen(false);
97102
}
98103
};
99104
Jodit.defaultOptions.controls.maximize = {
100-
template: () => IconToSVG(<ArrowMaximize16Regular title='Maximize' />),
105+
template: () => maxIcon,
101106
exec: (view: JoditExpanded) => {
102107
view.kwizInstance.props.onChange?.(view.value);//pass value from smaller editor to bigger one
103108
view.kwizInstance.setShowFullScreen(true);
104109
}
105110
};
106111
Jodit.defaultOptions.controls.minimize = {
107-
template: () => IconToSVG(<ArrowMinimize16Regular title='Minimize' />),
112+
template: () => minIcon,
108113
exec: (view: JoditExpanded) => {
109114
view.kwizInstance.props.onChange?.(view.value);
110115
view.kwizInstance.setShowFullScreen(false);

src/controls/progress-bar.tsx

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { DividerProps, makeStyles, mergeClasses, ProgressBar, tokens } from '@fluentui/react-components';
2+
import { CheckmarkRegular, FluentIcon } from '@fluentui/react-icons';
3+
import { isFunction, isNotEmptyString } from '@kwiz/common';
4+
import React from 'react';
5+
import { KnownClassNames } from '../styles/styles';
6+
import { Horizontal } from './horizontal';
7+
import { Section } from './section';
8+
import { Vertical } from './vertical';
9+
10+
const useStyles = makeStyles({
11+
root: {
12+
position: "relative"
13+
},
14+
stepNumber: {
15+
border: `2px solid ${tokens.colorNeutralStroke1}`,
16+
borderRadius: tokens.borderRadiusCircular,
17+
width: '24px',
18+
height: '24px',
19+
position: 'relative',
20+
display: 'inline-flex',
21+
alignItems: 'center',
22+
justifyContent: 'center',
23+
backgroundColor: tokens.colorNeutralBackground1,
24+
},
25+
stepLabel: {
26+
backgroundColor: tokens.colorNeutralBackground1,
27+
position: "absolute",
28+
top: '-10px',
29+
left: 0,
30+
right: 0,
31+
"& > span": {
32+
whiteSpace: "nowrap",
33+
overflow: "hidden",
34+
textOverflow: "ellipsis",
35+
display: "inline-block"
36+
}
37+
},
38+
stepNumberCurrent: {
39+
border: `2px solid ${tokens.colorBrandBackground}`,
40+
},
41+
stepNumberCompleted: {
42+
border: `2px solid ${tokens.colorBrandBackground}`,
43+
backgroundColor: tokens.colorBrandBackground,
44+
color: tokens.colorNeutralBackground1,
45+
},
46+
stepNumberClickable: {
47+
cursor: "pointer"
48+
},
49+
stepTitle: {
50+
fontSize: tokens.fontSizeBase400,
51+
lineHeight: tokens.lineHeightBase400,
52+
},
53+
progressBar: {
54+
position: "absolute",
55+
top: "14px"
56+
},
57+
stepSpacer: {
58+
position: "relative"
59+
}
60+
});
61+
interface IProps extends DividerProps {
62+
steps: number;
63+
step: number;
64+
stepLabel?: string;
65+
css?: string[];
66+
/** optional, send an icon instead of the step number */
67+
stepIcons?: FluentIcon[];
68+
onStepClick?: (step: number) => void;
69+
}
70+
export const ProgressBarEX = React.forwardRef<HTMLDivElement, (React.PropsWithChildren<IProps>)>((props, ref) => {
71+
const classes = useStyles();
72+
73+
let stepLabels: JSX.Element[] = [];
74+
for (let i = 0; i < props.steps; i++) {
75+
const stepClasses = [classes.stepNumber];
76+
let addLabel = false;
77+
let canClick = false;
78+
if (props.step === i) {
79+
stepClasses.push(classes.stepNumberCurrent);
80+
if (isNotEmptyString(props.stepLabel))
81+
addLabel = true;
82+
}
83+
else if (props.step > i) {
84+
stepClasses.push(classes.stepNumberCompleted);
85+
canClick = isFunction(props.onStepClick);
86+
if (canClick)
87+
stepClasses.push(classes.stepNumberClickable);
88+
}
89+
let StepIcon = props.stepIcons?.[i];
90+
stepLabels.push(<Section key={`step${i}`} css={stepClasses} onClick={canClick ? () => props.onStepClick(i) : undefined}>{StepIcon ? <StepIcon /> : `${i + 1}`}</Section>);
91+
stepLabels.push(<Section main key={`step${i}Spacer`} css={[classes.stepSpacer]}>
92+
{addLabel && <Horizontal key="label" hCentered css={[classes.stepLabel, KnownClassNames.progressBarStepLabel]}>
93+
<span>{props.stepLabel}</span>
94+
</Horizontal>}
95+
</Section>);
96+
97+
}
98+
99+
let StepIcon = props.stepIcons?.[props.steps];
100+
//add last submit step
101+
stepLabels.push(<span key='stepSubmit' className={mergeClasses(classes.stepNumber, props.step === props.steps && classes.stepNumberCompleted)}>{StepIcon ? <StepIcon /> : <CheckmarkRegular />}</span>);
102+
103+
return (
104+
<Vertical css={[classes.root, ...(props.css || [])]}>
105+
{/* progress bar first so labels will cover it without the need for zindex */}
106+
<ProgressBar className={classes.progressBar} value={(props.step * 2) + 1} max={props.steps * 2} />
107+
<Horizontal css={[classes.stepTitle]}>{...stepLabels}</Horizontal>
108+
</Vertical >
109+
);
110+
});

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export * from './controls/list';
2020
export * from './controls/loading';
2121
export * from './controls/menu';
2222
export * from './controls/please-wait';
23+
export * from './controls/progress-bar';
2324
export * from './controls/prompt';
2425
export * from './controls/qrcode';
2526
export * from './controls/search';
@@ -32,5 +33,5 @@ export { KWIZFluentContext, useKWIZFluentContext } from './helpers/context';
3233
export type { iKWIZFluentContext } from './helpers/context';
3334
export * from './helpers/drag-drop/exports';
3435
export * from './helpers/hooks';
35-
export { KnownClassNames, commonSizes } from './styles/styles';
36+
export { commonSizes, KnownClassNames } from './styles/styles';
3637

src/styles/styles.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export const KnownClassNames = {
6767
accordionBody: 'kfui-accordion-body',
6868
accordionBodyWrapper: 'kfui-accordion-body-wrapper',
6969
isOpen: 'is-opened',
70+
progressBarStepLabel: 'step-label'
7071
}
7172
export const useCommonStyles = makeStyles({
7273
printShow: {

0 commit comments

Comments
 (0)