@fibery/ui-kit 1.40.4 → 1.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/eslint.config.mjs +16 -0
- package/package.json +10 -9
- package/src/a11y-color.test.ts +5 -24
- package/src/actions-menu/actions-menu-item.tsx +6 -9
- package/src/actions-menu/context-actions-menu.tsx +8 -3
- package/src/ai/model.tsx +0 -1
- package/src/ai/temperature.tsx +0 -1
- package/src/animated-height-container.tsx +3 -3
- package/src/antd/index.tsx +3 -0
- package/src/antd/input.tsx +0 -1
- package/src/antd/styles.ts +9 -9
- package/src/app-icon-with-fallback.tsx +5 -5
- package/src/app-icon-wrapper.tsx +2 -3
- package/src/app-icon.tsx +3 -3
- package/src/avatar.tsx +3 -3
- package/src/button/base-button.tsx +0 -1
- package/src/button/make-button-colors.ts +2 -2
- package/src/checkbox.tsx +1 -1
- package/src/collapsible-section.tsx +11 -1
- package/src/color-utils.test.ts +15 -25
- package/src/color-utils.ts +43 -8
- package/src/command-menu/index.tsx +1 -0
- package/src/context-menu/index.tsx +5 -5
- package/src/date-picker/contexts.ts +2 -2
- package/src/date-picker/date-range-picker-popup-content.tsx +1 -2
- package/src/date-picker/date-range-picker.tsx +29 -31
- package/src/date-picker/single-date-picker-popup-content.tsx +1 -2
- package/src/date-picker/single-date-picker.tsx +149 -124
- package/src/date-picker/types.ts +4 -4
- package/src/day-select/iso-week-day-select.tsx +2 -2
- package/src/day-select/week-day-select.tsx +2 -2
- package/src/delayed.tsx +0 -1
- package/src/design-system/alpha.test.ts +59 -0
- package/src/design-system/alpha.ts +41 -0
- package/src/design-system/animation.ts +10 -0
- package/src/design-system/colors-css.test.ts +39 -0
- package/src/design-system/colors-css.ts +25 -0
- package/src/design-system/colors-js.test.ts +232 -0
- package/src/design-system/colors-js.ts +107 -0
- package/src/design-system/colors.test.ts +74 -0
- package/src/{design-system.colors.ts → design-system/colors.ts} +133 -134
- package/src/design-system/date.ts +3 -0
- package/src/design-system/layout.ts +106 -0
- package/src/design-system/theme.test.ts +94 -0
- package/src/design-system/theme.ts +93 -0
- package/src/design-system/typography.ts +179 -0
- package/src/design-system/vars.test.ts +1679 -0
- package/src/design-system/vars.ts +52 -0
- package/src/design-system.test.ts +46 -303
- package/src/design-system.ts +25 -545
- package/src/dropdown-menu/index.tsx +5 -5
- package/src/emoji-picker/icon-emoji-picker.tsx +1 -1
- package/src/fibermoji-placeholder.tsx +3 -3
- package/src/field-container.tsx +11 -2
- package/src/file-item/file-icon.tsx +3 -3
- package/src/file-item-2.tsx +4 -10
- package/src/file-item.tsx +0 -1
- package/src/icons/ast/ChatBubble.ts +8 -0
- package/src/icons/ast/ChatFloat.ts +8 -0
- package/src/icons/ast/ChatSidebar.ts +8 -0
- package/src/icons/ast/FileOther.ts +8 -0
- package/src/icons/ast/NoBorder.ts +8 -0
- package/src/icons/ast/NoFill.ts +8 -0
- package/src/icons/ast/index.tsx +6 -0
- package/src/icons/react/ChatBubble.tsx +13 -0
- package/src/icons/react/ChatFloat.tsx +13 -0
- package/src/icons/react/ChatSidebar.tsx +13 -0
- package/src/icons/react/FileOther.tsx +13 -0
- package/src/icons/react/NoBorder.tsx +13 -0
- package/src/icons/react/NoFill.tsx +13 -0
- package/src/icons/react/index.tsx +6 -0
- package/src/icons/svg/chat-bubble.svg +4 -0
- package/src/icons/svg/chat-float.svg +4 -0
- package/src/icons/svg/chat-sidebar.svg +4 -0
- package/src/icons/svg/file-other.svg +3 -0
- package/src/icons/svg/no-border.svg +17 -0
- package/src/icons/svg/no-fill.svg +4 -0
- package/src/images-gallery/images-gallery.tsx +8 -6
- package/src/images-gallery/slide-buttons.tsx +2 -2
- package/src/is-iOS.ts +0 -1
- package/src/is-in-popup.ts +2 -1
- package/src/lists/actions-menu-row-surface.tsx +4 -4
- package/src/loading-sausage.tsx +2 -2
- package/src/media-query-utils.ts +1 -2
- package/src/mobile-keyboard-aware-popup.tsx +2 -2
- package/src/modal-menu/modal-menu-content.tsx +2 -2
- package/src/number-input/decimal.js +9 -7
- package/src/number-input/{number-inline-input-with-autosize.tsx → number-input-inline-with-autosize.tsx} +20 -41
- package/src/number-input/number-input-inline.tsx +118 -0
- package/src/number-input/{index.test.js → number-input.test.tsx} +38 -43
- package/src/number-input/number-input.tsx +63 -121
- package/src/number-input/types.ts +19 -0
- package/src/number-input/utils.ts +61 -0
- package/src/online-users.tsx +2 -2
- package/src/palette-generator.test.ts +24 -281
- package/src/palette-generator.ts +39 -45
- package/src/palettes/_.ts +72 -0
- package/src/palettes/slate-arch.colors-snapshot.test.ts +1689 -0
- package/src/palettes/slate-arch.ts +188 -0
- package/src/palettes/slate-user.colors-snapshot.test.ts +1689 -0
- package/src/palettes/slate-user.ts +187 -0
- package/src/palettes/warm-arch.colors-snapshot.test.ts +1689 -0
- package/src/palettes/warm-arch.ts +222 -0
- package/src/palettes/warm-user.colors-snapshot.test.ts +1689 -0
- package/src/palettes/warm-user.ts +222 -0
- package/src/popover/get-element-ref.ts +28 -0
- package/src/popover/index.tsx +232 -333
- package/src/popover/{mobile-popup-context.tsx → mobile-popover-context.tsx} +7 -7
- package/src/popover/mobile-popover.tsx +169 -0
- package/src/popover/modifiers.tsx +2 -2
- package/src/popover/popup-stack-context.tsx +8 -9
- package/src/root-theme-provider.test.tsx +114 -19
- package/src/scale-generator.ts +31 -22
- package/src/select/components/menu-list-virtualized.tsx +5 -3
- package/src/select/index.tsx +6 -5
- package/src/select/select.tsx +36 -38
- package/src/select/util.ts +1 -1
- package/src/static-palettes.ts +221 -11
- package/src/thematic-controls.tsx +6 -6
- package/src/thematic-scales.tsx +15 -15
- package/src/thematic-state.ts +57 -20
- package/src/thematic.tsx +36 -32
- package/src/theme-provider.test.tsx +31 -19
- package/src/theme-provider.tsx +41 -38
- package/src/theme-settings.ts +66 -11
- package/src/theme-styles.ts +53 -8
- package/src/toast/toast.tsx +1 -2
- package/src/toggle.tsx +2 -2
- package/src/tooltip.tsx +4 -3
- package/src/type-badge.tsx +4 -11
- package/src/unit/styles.ts +0 -23
- package/src/use-is-phone.tsx +7 -2
- package/src/use-on-screen-keyboard-data.tsx +2 -2
- package/src/with-data.tsx +4 -3
- package/src/workflow-progress-icon.tsx +2 -2
- package/.eslintignore +0 -3
- package/.eslintrc +0 -14
- package/src/__mocks__/createInlineTheme.js +0 -3
- package/src/__snapshots__/design-system.test.ts.snap +0 -7265
- package/src/create-inline-theme.ts +0 -67
- package/src/number-input/index.js +0 -191
- package/src/palette.ts +0 -253
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {css, cx} from "@linaria/core";
|
|
2
2
|
import {useCallback, useMemo, useRef, useState} from "react";
|
|
3
|
-
import
|
|
4
|
-
import _ from "lodash";
|
|
3
|
+
import identity from "lodash/identity";
|
|
5
4
|
import {add, sub} from "./decimal";
|
|
6
5
|
import InputNumber from "rc-input-number";
|
|
7
6
|
import {StepButton, ValueWithStepperWrapper} from "./step-button";
|
|
@@ -10,6 +9,8 @@ import {UnitPrimitive} from "../unit/primitive";
|
|
|
10
9
|
import {Stack} from "./stack";
|
|
11
10
|
import {hiddenCss, inputStyle, textInlineStyle} from "./styles";
|
|
12
11
|
import {invalidInputStyle} from "./edit-unit-styles";
|
|
12
|
+
import {NumberInputProps, Value} from "./types";
|
|
13
|
+
import {formatValue, getStep, parseValue} from "./utils";
|
|
13
14
|
|
|
14
15
|
const invalidCss = css``;
|
|
15
16
|
|
|
@@ -36,35 +37,25 @@ const inputCss = css`
|
|
|
36
37
|
}
|
|
37
38
|
`;
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
type Props = NumberInputProps & {
|
|
41
|
+
width?: number;
|
|
42
|
+
invalid?: boolean;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const NumberInputInlineWithAutosize = ({
|
|
40
46
|
value,
|
|
41
47
|
numberFormat,
|
|
42
48
|
numberPrecision,
|
|
43
|
-
format =
|
|
44
|
-
onFocus,
|
|
45
|
-
onBlur,
|
|
46
|
-
onChange,
|
|
47
|
-
onKeyDown,
|
|
49
|
+
format = identity,
|
|
48
50
|
autoFocus,
|
|
49
51
|
placeholder,
|
|
50
52
|
invalid,
|
|
53
|
+
onFocus,
|
|
54
|
+
onBlur,
|
|
55
|
+
onChange = () => {},
|
|
56
|
+
onKeyDown,
|
|
51
57
|
...rest
|
|
52
|
-
}: {
|
|
53
|
-
value: string | number | null;
|
|
54
|
-
numberFormat: "Number" | "Money" | "Percent";
|
|
55
|
-
format?: (value: string | number) => string;
|
|
56
|
-
numberPrecision: number;
|
|
57
|
-
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
|
58
|
-
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
|
59
|
-
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
60
|
-
onChange: (value: string | number) => void;
|
|
61
|
-
decimalSeparator?: string;
|
|
62
|
-
placeholder?: string;
|
|
63
|
-
autoFocus: boolean;
|
|
64
|
-
style?: object;
|
|
65
|
-
width?: number;
|
|
66
|
-
invalid?: boolean;
|
|
67
|
-
}) => {
|
|
58
|
+
}: Props) => {
|
|
68
59
|
const [isEditing, setIsEditing] = useState(false);
|
|
69
60
|
const handleFocus: React.FocusEventHandler<HTMLInputElement> = useCallback(
|
|
70
61
|
(e) => {
|
|
@@ -89,30 +80,18 @@ export const NumberInlineInputWithAutosize = ({
|
|
|
89
80
|
);
|
|
90
81
|
|
|
91
82
|
const handleChange = useCallback(
|
|
92
|
-
(value:
|
|
93
|
-
|
|
94
|
-
onChange(parseValue(value, numberFormat, numberPrecision));
|
|
95
|
-
}
|
|
83
|
+
(value: Value) => {
|
|
84
|
+
onChange(parseValue(value, numberFormat, numberPrecision));
|
|
96
85
|
},
|
|
97
86
|
[onChange, numberFormat, numberPrecision]
|
|
98
87
|
);
|
|
99
88
|
|
|
100
89
|
const handleStep = useCallback(
|
|
101
|
-
(
|
|
102
|
-
newValue: string | number,
|
|
103
|
-
{
|
|
104
|
-
type,
|
|
105
|
-
}: {
|
|
106
|
-
type: "up" | "down";
|
|
107
|
-
}
|
|
108
|
-
) => {
|
|
90
|
+
(newValue: string | number, {type}: {type: "up" | "down"}) => {
|
|
109
91
|
inputRef.current?.focus();
|
|
110
92
|
const step = getStep(numberFormat);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
} else {
|
|
114
|
-
onChange(sub(value, step));
|
|
115
|
-
}
|
|
93
|
+
const nextValue = type === "up" ? add(value, step) : sub(value, step);
|
|
94
|
+
onChange(nextValue);
|
|
116
95
|
},
|
|
117
96
|
[numberFormat, onChange, value]
|
|
118
97
|
);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {textStyles, themeVars} from "../../src/design-system";
|
|
2
|
+
import {css} from "@linaria/core";
|
|
3
|
+
import {ComponentProps, forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState} from "react";
|
|
4
|
+
import {add, sub} from "./decimal";
|
|
5
|
+
import InputNumber from "rc-input-number";
|
|
6
|
+
import {StepButton} from "./step-button";
|
|
7
|
+
import {stopPropagation} from "@fibery/react/src/stop-propagation";
|
|
8
|
+
import cx from "classnames";
|
|
9
|
+
import {invalidInputStyle} from "./edit-unit-styles";
|
|
10
|
+
import {formatValue, getStep, parseValue} from "./utils";
|
|
11
|
+
import {NumberInputProps} from "./types";
|
|
12
|
+
|
|
13
|
+
const invalidCss = css``;
|
|
14
|
+
|
|
15
|
+
const inputContainerCss = css`
|
|
16
|
+
position: relative;
|
|
17
|
+
input {
|
|
18
|
+
background-color: ${themeVars.transparent};
|
|
19
|
+
${textStyles.regular}
|
|
20
|
+
outline: none;
|
|
21
|
+
width: 100%;
|
|
22
|
+
border: none;
|
|
23
|
+
text-align: right;
|
|
24
|
+
&::placeholder {
|
|
25
|
+
color: ${themeVars.inputPlaceholderTextColor};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&.${invalidCss} {
|
|
30
|
+
${invalidInputStyle}
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
type Props = ComponentProps<typeof InputNumber> &
|
|
35
|
+
NumberInputProps & {
|
|
36
|
+
valueWrapperClassName?: string;
|
|
37
|
+
invalid?: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const NumberInputInline = forwardRef<HTMLInputElement, Props>(
|
|
41
|
+
(
|
|
42
|
+
{
|
|
43
|
+
value,
|
|
44
|
+
numberFormat,
|
|
45
|
+
numberPrecision,
|
|
46
|
+
format,
|
|
47
|
+
autoFocus,
|
|
48
|
+
placeholder,
|
|
49
|
+
valueWrapperClassName,
|
|
50
|
+
invalid,
|
|
51
|
+
onFocus,
|
|
52
|
+
onBlur,
|
|
53
|
+
onChange = () => {},
|
|
54
|
+
onKeyDown,
|
|
55
|
+
...rest
|
|
56
|
+
},
|
|
57
|
+
ref
|
|
58
|
+
) => {
|
|
59
|
+
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
60
|
+
const [inputContainerRef, setInputContainerRef] = useState<HTMLDivElement | null>(null);
|
|
61
|
+
|
|
62
|
+
useImperativeHandle(ref, () => inputRef.current!, []);
|
|
63
|
+
|
|
64
|
+
const handleBlur = useCallback(
|
|
65
|
+
(e: React.FocusEvent<HTMLInputElement>) => {
|
|
66
|
+
if (e.relatedTarget && inputContainerRef && !inputContainerRef.contains(e.relatedTarget)) {
|
|
67
|
+
onBlur?.(e);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
[inputContainerRef, onBlur]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const handleChange = useCallback(
|
|
74
|
+
(value: string | number | null) => {
|
|
75
|
+
if (onChange) {
|
|
76
|
+
onChange(parseValue(value, numberFormat, numberPrecision));
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
[onChange, numberFormat, numberPrecision]
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const handleStep = useCallback(
|
|
83
|
+
(newValue: string | number, {type}: {type: "up" | "down"}) => {
|
|
84
|
+
inputRef.current?.focus();
|
|
85
|
+
const step = getStep(numberFormat);
|
|
86
|
+
const nextValue = type === "up" ? add(value, step) : sub(value, step);
|
|
87
|
+
onChange(nextValue);
|
|
88
|
+
},
|
|
89
|
+
[numberFormat, onChange, value]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const formattedValue = useMemo(() => {
|
|
93
|
+
const isEditing = true;
|
|
94
|
+
return formatValue(value, isEditing, numberFormat, format);
|
|
95
|
+
}, [value, numberFormat, format]);
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div ref={setInputContainerRef} className={cx(valueWrapperClassName, inputContainerCss, invalid && invalidCss)}>
|
|
99
|
+
<InputNumber
|
|
100
|
+
autoFocus={autoFocus}
|
|
101
|
+
ref={inputRef}
|
|
102
|
+
value={formattedValue}
|
|
103
|
+
type="text"
|
|
104
|
+
placeholder={placeholder}
|
|
105
|
+
upHandler={<StepButton type={"up"} />}
|
|
106
|
+
downHandler={<StepButton type={"down"} />}
|
|
107
|
+
onFocus={onFocus}
|
|
108
|
+
onBlur={handleBlur}
|
|
109
|
+
onChange={handleChange}
|
|
110
|
+
onKeyDown={onKeyDown}
|
|
111
|
+
onStep={handleStep}
|
|
112
|
+
onClick={stopPropagation}
|
|
113
|
+
{...rest}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
);
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import {useState} from "react";
|
|
2
|
-
import {
|
|
3
|
-
import {InputNumber} from "./index";
|
|
2
|
+
import {NumberInput} from "./number-input";
|
|
4
3
|
import {render, screen} from "@testing-library/react";
|
|
5
4
|
import userEvent from "@testing-library/user-event";
|
|
6
5
|
import {TooltipProvider} from "../tooltip";
|
|
7
6
|
|
|
8
|
-
describe("
|
|
9
|
-
const theme = {opacity: {}};
|
|
10
|
-
|
|
7
|
+
describe("number input", () => {
|
|
11
8
|
const getArrowUp = () => screen.getByRole("button", {name: "Increase Value"});
|
|
12
9
|
const getArrowDown = () => screen.getByRole("button", {name: "Decrease Value"});
|
|
13
10
|
|
|
14
|
-
const makePercentFormatter = (precision) =>
|
|
11
|
+
const makePercentFormatter = (precision: number) =>
|
|
15
12
|
jest.fn().mockImplementation((value) => {
|
|
16
13
|
const formatter = new Intl.NumberFormat("en-US", {
|
|
17
14
|
style: "percent",
|
|
@@ -26,17 +23,17 @@ describe("InputNumber", () => {
|
|
|
26
23
|
it("renders in percent format", () => {
|
|
27
24
|
const formatPercent = makePercentFormatter(2);
|
|
28
25
|
render(
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
</
|
|
26
|
+
<TooltipProvider>
|
|
27
|
+
<NumberInput
|
|
28
|
+
data-testid="test-input"
|
|
29
|
+
value={0.01}
|
|
30
|
+
numberFormat={"Percent"}
|
|
31
|
+
numberPrecision={2}
|
|
32
|
+
decimalSeparator="."
|
|
33
|
+
format={formatPercent}
|
|
34
|
+
onChange={() => {}}
|
|
35
|
+
/>
|
|
36
|
+
</TooltipProvider>
|
|
40
37
|
);
|
|
41
38
|
|
|
42
39
|
expect(screen.getByTestId("test-input")).toHaveDisplayValue("1.00%");
|
|
@@ -45,20 +42,19 @@ describe("InputNumber", () => {
|
|
|
45
42
|
it("editing without percent format, applies percent format after blur", async () => {
|
|
46
43
|
const formatPercent = makePercentFormatter(2);
|
|
47
44
|
const Demo = () => {
|
|
48
|
-
const [value, setValue] = useState(null);
|
|
45
|
+
const [value, setValue] = useState<string | null>(null);
|
|
49
46
|
return (
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
</ThemeProvider>
|
|
47
|
+
<TooltipProvider>
|
|
48
|
+
<NumberInput
|
|
49
|
+
data-testid="test-input"
|
|
50
|
+
value={value}
|
|
51
|
+
numberFormat={"Percent"}
|
|
52
|
+
numberPrecision={2}
|
|
53
|
+
decimalSeparator="."
|
|
54
|
+
format={formatPercent}
|
|
55
|
+
onChange={setValue}
|
|
56
|
+
/>
|
|
57
|
+
</TooltipProvider>
|
|
62
58
|
);
|
|
63
59
|
};
|
|
64
60
|
render(<Demo />);
|
|
@@ -75,20 +71,19 @@ describe("InputNumber", () => {
|
|
|
75
71
|
it("arrows increase/decrease by one percent", async () => {
|
|
76
72
|
const formatPercent = makePercentFormatter(2);
|
|
77
73
|
const Demo = () => {
|
|
78
|
-
const [value, setValue] = useState(0.05);
|
|
74
|
+
const [value, setValue] = useState<string | number | null>(0.05);
|
|
79
75
|
return (
|
|
80
|
-
<
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
</ThemeProvider>
|
|
76
|
+
<TooltipProvider>
|
|
77
|
+
<NumberInput
|
|
78
|
+
data-testid="test-input"
|
|
79
|
+
value={value}
|
|
80
|
+
numberFormat={"Percent"}
|
|
81
|
+
numberPrecision={2}
|
|
82
|
+
decimalSeparator="."
|
|
83
|
+
format={formatPercent}
|
|
84
|
+
onChange={setValue}
|
|
85
|
+
/>
|
|
86
|
+
</TooltipProvider>
|
|
92
87
|
);
|
|
93
88
|
};
|
|
94
89
|
|
|
@@ -1,133 +1,75 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {ComponentProps, forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState} from "react";
|
|
4
|
-
import {formatValue, getStep, parseValue} from "./index";
|
|
5
|
-
import _ from "lodash";
|
|
1
|
+
import {useCallback, useMemo, useState} from "react";
|
|
2
|
+
import {AntInputNumber} from "../antd/input-number";
|
|
6
3
|
import {add, sub} from "./decimal";
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import cx from "classnames";
|
|
11
|
-
import {invalidInputStyle} from "./edit-unit-styles";
|
|
4
|
+
import {composeEventHandlers} from "@fibery/react/src/compose-event-handlers";
|
|
5
|
+
import {NumberInputProps} from "./types";
|
|
6
|
+
import {formatValue, getStep, isEmpty, parseValue} from "./utils";
|
|
12
7
|
|
|
13
|
-
const
|
|
8
|
+
export const NumberInput = ({
|
|
9
|
+
className,
|
|
10
|
+
value,
|
|
11
|
+
numberFormat,
|
|
12
|
+
numberPrecision,
|
|
13
|
+
format,
|
|
14
|
+
onFocus,
|
|
15
|
+
onBlur,
|
|
16
|
+
onChange = () => {},
|
|
17
|
+
...rest
|
|
18
|
+
}: NumberInputProps) => {
|
|
19
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
14
20
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
input {
|
|
18
|
-
background-color: ${themeVars.transparent};
|
|
19
|
-
${textStyles.regular}
|
|
20
|
-
outline: none;
|
|
21
|
-
width: 100%;
|
|
22
|
-
border: none;
|
|
23
|
-
text-align: right;
|
|
24
|
-
&::placeholder {
|
|
25
|
-
color: ${themeVars.inputPlaceholderTextColor};
|
|
26
|
-
}
|
|
27
|
-
}
|
|
21
|
+
const handleFocus = composeEventHandlers(onFocus, () => setIsEditing(true));
|
|
22
|
+
const handleBlur = composeEventHandlers(onBlur, () => setIsEditing(false));
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
type Props = ComponentProps<typeof InputNumber> & {
|
|
37
|
-
value: string | number | null;
|
|
38
|
-
numberFormat: NumberFormat;
|
|
39
|
-
format?: (value: string | number) => string;
|
|
40
|
-
numberPrecision: number;
|
|
41
|
-
placeholder: string;
|
|
42
|
-
autoFocus: boolean;
|
|
43
|
-
valueWrapperClassName?: string;
|
|
44
|
-
decimalSeparator?: string;
|
|
45
|
-
invalid?: boolean;
|
|
46
|
-
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
|
47
|
-
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
|
48
|
-
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
49
|
-
onChange: (value: string | number | null) => void;
|
|
50
|
-
};
|
|
24
|
+
const handleChange = useCallback(
|
|
25
|
+
(value: string | number | null) => {
|
|
26
|
+
const nextValue = parseValue(value, numberFormat, numberPrecision);
|
|
27
|
+
onChange(nextValue);
|
|
28
|
+
},
|
|
29
|
+
[onChange, numberFormat, numberPrecision]
|
|
30
|
+
);
|
|
51
31
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
value,
|
|
56
|
-
|
|
57
|
-
numberPrecision,
|
|
58
|
-
format = _.identity,
|
|
59
|
-
onFocus,
|
|
60
|
-
onBlur,
|
|
61
|
-
onChange,
|
|
62
|
-
onKeyDown,
|
|
63
|
-
autoFocus,
|
|
64
|
-
placeholder,
|
|
65
|
-
valueWrapperClassName,
|
|
66
|
-
invalid,
|
|
67
|
-
...rest
|
|
32
|
+
const handleStep = useCallback(
|
|
33
|
+
(newValue: string | number, {type}: {type: "up" | "down"}) => {
|
|
34
|
+
const step = getStep(numberFormat);
|
|
35
|
+
const nextValue = type === "up" ? add(value, step) : sub(value, step);
|
|
36
|
+
onChange(nextValue);
|
|
68
37
|
},
|
|
69
|
-
|
|
70
|
-
)
|
|
71
|
-
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
72
|
-
const [inputContainerRef, setInputContainerRef] = useState<HTMLDivElement | null>(null);
|
|
38
|
+
[value, numberFormat, onChange]
|
|
39
|
+
);
|
|
73
40
|
|
|
74
|
-
|
|
41
|
+
const formattedValue = useMemo(() => {
|
|
42
|
+
return formatValue(value, isEditing, numberFormat, format);
|
|
43
|
+
}, [value, isEditing, numberFormat, format]);
|
|
75
44
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
45
|
+
return (
|
|
46
|
+
<AntInputNumber
|
|
47
|
+
className={className}
|
|
48
|
+
value={formattedValue}
|
|
49
|
+
onFocus={handleFocus}
|
|
50
|
+
onBlur={handleBlur}
|
|
51
|
+
onChange={handleChange}
|
|
52
|
+
onStep={handleStep}
|
|
53
|
+
{...rest}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
type FieldProp = "value" | "onFocus" | "onBlur" | "onChange";
|
|
84
59
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
},
|
|
91
|
-
[onChange, numberFormat, numberPrecision]
|
|
92
|
-
);
|
|
60
|
+
type NumberInputFieldProps = {
|
|
61
|
+
input: Pick<NumberInputProps, FieldProp> & {
|
|
62
|
+
onBlur: (value: string | number | null) => void;
|
|
63
|
+
};
|
|
64
|
+
} & Omit<NumberInputProps, FieldProp>;
|
|
93
65
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const step = getStep(numberFormat);
|
|
98
|
-
if (type === "up") {
|
|
99
|
-
onChange(add(value, step));
|
|
100
|
-
} else {
|
|
101
|
-
onChange(sub(value, step));
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
[numberFormat, onChange, value]
|
|
105
|
-
);
|
|
66
|
+
export const NumberInputField = (props: NumberInputFieldProps) => {
|
|
67
|
+
const {input, ...rest} = props;
|
|
68
|
+
const {value, onBlur, ...inputRest} = input;
|
|
106
69
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}, [value, numberFormat, format]);
|
|
70
|
+
const handleBlur = useCallback(() => {
|
|
71
|
+
onBlur(isEmpty(value) ? null : value);
|
|
72
|
+
}, [onBlur, value]);
|
|
111
73
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<InputNumber
|
|
115
|
-
autoFocus={autoFocus}
|
|
116
|
-
ref={inputRef}
|
|
117
|
-
value={formattedValue}
|
|
118
|
-
type="text"
|
|
119
|
-
placeholder={placeholder}
|
|
120
|
-
upHandler={<StepButton type={"up"} />}
|
|
121
|
-
downHandler={<StepButton type={"down"} />}
|
|
122
|
-
onFocus={onFocus}
|
|
123
|
-
onBlur={handleBlur}
|
|
124
|
-
onChange={handleChange}
|
|
125
|
-
onKeyDown={onKeyDown}
|
|
126
|
-
onStep={handleStep}
|
|
127
|
-
onClick={stopPropagation}
|
|
128
|
-
{...rest}
|
|
129
|
-
/>
|
|
130
|
-
</div>
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
);
|
|
74
|
+
return <NumberInput value={value} onBlur={handleBlur} {...inputRest} {...rest} />;
|
|
75
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type NumberFormat = "Number" | "Money" | "Percent";
|
|
2
|
+
|
|
3
|
+
export type Value = number | string | null | undefined;
|
|
4
|
+
|
|
5
|
+
export type NumberInputProps = {
|
|
6
|
+
value: Value;
|
|
7
|
+
numberFormat: NumberFormat;
|
|
8
|
+
numberPrecision: number;
|
|
9
|
+
decimalSeparator: string;
|
|
10
|
+
format?: (value: Value) => string;
|
|
11
|
+
controls?: boolean;
|
|
12
|
+
onChange?: (value: string | null) => void;
|
|
13
|
+
} & Pick<
|
|
14
|
+
React.ComponentProps<"input">,
|
|
15
|
+
"className" | "autoFocus" | "placeholder" | "style" | "disabled" | "onFocus" | "onBlur" | "onKeyDown"
|
|
16
|
+
> & {
|
|
17
|
+
// TODO: coming from and, get rid of it after migrating from ant
|
|
18
|
+
wrapClassName?: string;
|
|
19
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {NumberFormat, Value} from "./types";
|
|
2
|
+
import {divideByHundred, multiplyByHundred} from "./decimal";
|
|
3
|
+
|
|
4
|
+
export const isEmpty = (value: Value) => {
|
|
5
|
+
return value === null || value === undefined || value === "";
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const getStep = (numberFormat: NumberFormat) => {
|
|
9
|
+
return numberFormat === "Percent" ? 0.01 : 1;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const formatValue = (
|
|
13
|
+
value: Value,
|
|
14
|
+
isEditing: boolean,
|
|
15
|
+
numberFormat: NumberFormat,
|
|
16
|
+
format?: (value: Value) => string
|
|
17
|
+
) => {
|
|
18
|
+
if (isEmpty(value)) {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const externalize = (value: Value): Value => {
|
|
23
|
+
if (isEditing) {
|
|
24
|
+
return numberFormat === "Percent" ? multiplyByHundred(value) : value;
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const maybeFormat = (value: Value) => {
|
|
30
|
+
if (isEditing || !format) {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
return format(value);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return maybeFormat(externalize(value));
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const parseValue = (value: Value, numberFormat: NumberFormat, numberPrecision: number): string | null => {
|
|
40
|
+
if (isEmpty(value)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const parse = (value: Value) => {
|
|
45
|
+
const parsed = numberPrecision === 0 ? parseInt(value as string) : parseFloat(value as string);
|
|
46
|
+
return isNaN(parsed) ? null : parsed;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const internalizeNumber = (value: number | null) => {
|
|
50
|
+
if (value === null) {
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
return numberFormat === "Percent" ? divideByHundred(value) : value;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const toString = (value: number | null) => {
|
|
57
|
+
return value === null ? value : String(value);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return toString(internalizeNumber(parse(value)));
|
|
61
|
+
};
|
package/src/online-users.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {css, cx} from "@linaria/core";
|
|
2
|
-
import
|
|
2
|
+
import take from "lodash/take";
|
|
3
3
|
import {useEffect, useRef, useState} from "react";
|
|
4
4
|
import {AvatarImage} from "./avatar";
|
|
5
5
|
import {getObjectColorMemoized, space, themeVars, tooltipDelay} from "./design-system";
|
|
@@ -148,7 +148,7 @@ export function OnlineUsers({
|
|
|
148
148
|
</Tooltip>
|
|
149
149
|
</div>
|
|
150
150
|
|
|
151
|
-
{
|
|
151
|
+
{take(users, users.length === maxVisibleUsers ? maxVisibleUsers : maxVisibleUsers - 1).map(
|
|
152
152
|
({avatarUrl, hasAccess, name, callBack, isFollowed}, index) => {
|
|
153
153
|
const tooltipDescription = callBack ? "Click to follow" : "";
|
|
154
154
|
const tooltipTitle = hasAccess ? name : "Private User";
|