@fibery/ui-kit 1.40.3 → 1.41.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.
Files changed (174) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/eslint.config.mjs +16 -0
  3. package/package.json +11 -13
  4. package/src/a11y-color.test.ts +162 -0
  5. package/src/a11y-color.ts +13 -20
  6. package/src/actions-menu/actions-menu-item.tsx +9 -12
  7. package/src/actions-menu/context-actions-menu.tsx +8 -3
  8. package/src/ai/model.tsx +0 -1
  9. package/src/ai/temperature.tsx +0 -1
  10. package/src/animated-height-container.tsx +3 -3
  11. package/src/antd/ant-modal.tsx +10 -5
  12. package/src/antd/index.tsx +3 -0
  13. package/src/antd/input.tsx +0 -1
  14. package/src/antd/styles.ts +15 -15
  15. package/src/app-icon-with-fallback.tsx +6 -6
  16. package/src/app-icon-wrapper.tsx +2 -3
  17. package/src/app-icon.tsx +4 -4
  18. package/src/avatar.tsx +3 -3
  19. package/src/button/base-button.tsx +0 -1
  20. package/src/button/button.tsx +1 -0
  21. package/src/button/make-button-colors.ts +5 -13
  22. package/src/checkbox.tsx +2 -2
  23. package/src/collapsible-section.tsx +11 -1
  24. package/src/color-picker/ColorPickerOrLoader.js +2 -2
  25. package/src/color-utils.test.ts +307 -0
  26. package/src/color-utils.ts +215 -0
  27. package/src/command-menu/index.tsx +1 -0
  28. package/src/comment.tsx +3 -2
  29. package/src/context-menu/index.tsx +12 -7
  30. package/src/date-picker/contexts.ts +2 -2
  31. package/src/date-picker/date-range-picker.tsx +3 -4
  32. package/src/date-picker/single-date-picker.tsx +147 -119
  33. package/src/date-picker/types.ts +4 -3
  34. package/src/day-select/iso-week-day-select.tsx +2 -2
  35. package/src/day-select/week-day-select.tsx +2 -2
  36. package/src/delayed.tsx +0 -1
  37. package/src/design-system/alpha.test.ts +59 -0
  38. package/src/design-system/alpha.ts +41 -0
  39. package/src/design-system/animation.ts +10 -0
  40. package/src/design-system/colors-css.test.ts +39 -0
  41. package/src/design-system/colors-css.ts +25 -0
  42. package/src/design-system/colors-js.test.ts +232 -0
  43. package/src/design-system/colors-js.ts +107 -0
  44. package/src/design-system/colors.test.ts +74 -0
  45. package/src/design-system/colors.ts +759 -0
  46. package/src/design-system/date.ts +3 -0
  47. package/src/design-system/layout.ts +106 -0
  48. package/src/design-system/theme.test.ts +94 -0
  49. package/src/design-system/theme.ts +93 -0
  50. package/src/design-system/typography.ts +179 -0
  51. package/src/design-system/vars.test.ts +1679 -0
  52. package/src/design-system/vars.ts +52 -0
  53. package/src/design-system.test.ts +45 -22
  54. package/src/design-system.ts +25 -1339
  55. package/src/dropdown-menu/index.tsx +21 -16
  56. package/src/emoji-picker/app-icon-picker.tsx +5 -5
  57. package/src/emoji-picker/emoji-picker-content-with-color.tsx +4 -4
  58. package/src/emoji-picker/icon-emoji-picker.tsx +3 -3
  59. package/src/favorites-icon.tsx +1 -1
  60. package/src/fibermoji-placeholder.tsx +3 -3
  61. package/src/field-container.tsx +11 -2
  62. package/src/file-item/file-icon.tsx +169 -0
  63. package/src/file-item/file-menu-items.tsx +68 -0
  64. package/src/file-item/file-preview-actions.tsx +38 -0
  65. package/src/file-item/file-title.tsx +48 -0
  66. package/src/file-item/types.ts +27 -0
  67. package/src/file-item/use-register-in-image-gallery.tsx +70 -0
  68. package/src/file-item-2.tsx +32 -348
  69. package/src/file-item.tsx +6 -3
  70. package/src/hue-shift.test.ts +91 -0
  71. package/src/icons/ast/ChatBubble.ts +8 -0
  72. package/src/icons/ast/ChatFloat.ts +8 -0
  73. package/src/icons/ast/ChatSidebar.ts +8 -0
  74. package/src/icons/ast/FileCounter.ts +8 -0
  75. package/src/icons/ast/FileMultiple.ts +8 -0
  76. package/src/icons/ast/FileOther.ts +8 -0
  77. package/src/icons/ast/NoBorder.ts +8 -0
  78. package/src/icons/ast/NoFill.ts +8 -0
  79. package/src/icons/ast/ValueEdit.ts +8 -0
  80. package/src/icons/ast/index.tsx +9 -0
  81. package/src/icons/react/ChatBubble.tsx +13 -0
  82. package/src/icons/react/ChatFloat.tsx +13 -0
  83. package/src/icons/react/ChatSidebar.tsx +13 -0
  84. package/src/icons/react/FileCounter.tsx +13 -0
  85. package/src/icons/react/FileMultiple.tsx +13 -0
  86. package/src/icons/react/FileOther.tsx +13 -0
  87. package/src/icons/react/NoBorder.tsx +13 -0
  88. package/src/icons/react/NoFill.tsx +13 -0
  89. package/src/icons/react/ValueEdit.tsx +13 -0
  90. package/src/icons/react/index.tsx +9 -0
  91. package/src/icons/svg/chat-bubble.svg +4 -0
  92. package/src/icons/svg/chat-float.svg +4 -0
  93. package/src/icons/svg/chat-sidebar.svg +4 -0
  94. package/src/icons/svg/file-counter.svg +3 -0
  95. package/src/icons/svg/file-multiple.svg +3 -0
  96. package/src/icons/svg/file-other.svg +3 -0
  97. package/src/icons/svg/no-border.svg +17 -0
  98. package/src/icons/svg/no-fill.svg +4 -0
  99. package/src/icons/svg/value-edit.svg +3 -0
  100. package/src/images-gallery/images-gallery.tsx +8 -6
  101. package/src/images-gallery/slide-buttons.tsx +4 -11
  102. package/src/is-iOS.ts +0 -1
  103. package/src/is-in-popup.ts +2 -1
  104. package/src/lists/actions-menu-row-surface.tsx +7 -7
  105. package/src/loading-sausage.tsx +2 -2
  106. package/src/media-query-utils.ts +1 -2
  107. package/src/mobile-keyboard-aware-popup.tsx +6 -5
  108. package/src/modal-menu/modal-menu-content.tsx +2 -2
  109. package/src/number-input/decimal.js +9 -7
  110. package/src/number-input/{number-inline-input-with-autosize.tsx → number-input-inline-with-autosize.tsx} +20 -41
  111. package/src/number-input/number-input-inline.tsx +118 -0
  112. package/src/number-input/{index.test.js → number-input.test.tsx} +38 -43
  113. package/src/number-input/number-input.tsx +63 -121
  114. package/src/number-input/types.ts +19 -0
  115. package/src/number-input/utils.ts +61 -0
  116. package/src/online-users.tsx +2 -2
  117. package/src/palette-generator.test.ts +309 -0
  118. package/src/palette-generator.ts +160 -0
  119. package/src/palettes/_.ts +72 -0
  120. package/src/palettes/slate-arch.colors-snapshot.test.ts +1689 -0
  121. package/src/palettes/slate-arch.ts +188 -0
  122. package/src/palettes/slate-user.colors-snapshot.test.ts +1689 -0
  123. package/src/palettes/slate-user.ts +187 -0
  124. package/src/palettes/warm-arch.colors-snapshot.test.ts +1689 -0
  125. package/src/palettes/warm-arch.ts +222 -0
  126. package/src/palettes/warm-user.colors-snapshot.test.ts +1689 -0
  127. package/src/palettes/warm-user.ts +222 -0
  128. package/src/platform.ts +0 -3
  129. package/src/popover/get-element-ref.ts +28 -0
  130. package/src/popover/index.tsx +236 -339
  131. package/src/popover/{mobile-popup-context.tsx → mobile-popover-context.tsx} +7 -7
  132. package/src/popover/mobile-popover.tsx +169 -0
  133. package/src/popover/modifiers.tsx +2 -2
  134. package/src/popover/popup-stack-context.tsx +8 -9
  135. package/src/progress.tsx +2 -2
  136. package/src/reactions/reaction-button.tsx +12 -6
  137. package/src/root-theme-provider.test.tsx +411 -0
  138. package/src/scale-generator.ts +356 -0
  139. package/src/select/components/menu-list-virtualized.tsx +12 -25
  140. package/src/select/components/menu.tsx +12 -2
  141. package/src/select/index.tsx +6 -5
  142. package/src/select/select.tsx +38 -39
  143. package/src/select/styles.ts +0 -1
  144. package/src/select/util.ts +1 -1
  145. package/src/static-palettes.ts +356 -0
  146. package/src/thematic-color-picker.tsx +266 -0
  147. package/src/thematic-constants.tsx +27 -0
  148. package/src/thematic-controls.tsx +144 -0
  149. package/src/thematic-scales.tsx +122 -0
  150. package/src/thematic-state.ts +370 -0
  151. package/src/thematic.tsx +386 -0
  152. package/src/theme-provider.test.tsx +820 -0
  153. package/src/theme-provider.tsx +158 -92
  154. package/src/theme-settings.ts +67 -12
  155. package/src/theme-styles.ts +58 -6
  156. package/src/toast/toast-action.tsx +1 -1
  157. package/src/toast/toast.tsx +1 -2
  158. package/src/toggle-on-off.tsx +2 -2
  159. package/src/toggle.tsx +7 -8
  160. package/src/tooltip.tsx +14 -10
  161. package/src/type-badge.tsx +7 -14
  162. package/src/unit/styles.ts +2 -25
  163. package/src/unit/unit-with-tooltip.tsx +3 -2
  164. package/src/use-is-phone.tsx +7 -2
  165. package/src/use-long-press.tsx +2 -2
  166. package/src/use-on-screen-keyboard-data.tsx +2 -2
  167. package/src/with-data.tsx +4 -3
  168. package/src/workflow-progress-icon.tsx +2 -2
  169. package/.eslintignore +0 -3
  170. package/.eslintrc +0 -14
  171. package/src/__mocks__/createInlineTheme.js +0 -3
  172. package/src/create-inline-theme.ts +0 -66
  173. package/src/number-input/index.js +0 -191
  174. package/src/palette.ts +0 -237
@@ -1,7 +1,6 @@
1
1
  import {css, cx} from "@linaria/core";
2
2
  import {useCallback, useMemo, useRef, useState} from "react";
3
- import {formatValue, getStep, parseValue} from "./index";
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
- export const NumberInlineInputWithAutosize = ({
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 = _.identity,
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: string | number | null) => {
93
- if (onChange) {
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
- if (type === "up") {
112
- onChange(add(value, step));
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 {ThemeProvider} from "../theme-provider";
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("InputNumber", () => {
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
- <ThemeProvider theme={theme}>
30
- <TooltipProvider>
31
- <InputNumber
32
- data-testid="test-input"
33
- value={0.01}
34
- numberFormat={"Percent"}
35
- numberPrecision={2}
36
- format={formatPercent}
37
- />
38
- </TooltipProvider>
39
- </ThemeProvider>
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
- <ThemeProvider theme={theme}>
51
- <TooltipProvider>
52
- <InputNumber
53
- data-testid="test-input"
54
- value={value}
55
- numberFormat={"Percent"}
56
- numberPrecision={2}
57
- format={formatPercent}
58
- onChange={setValue}
59
- />
60
- </TooltipProvider>
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
- <ThemeProvider theme={theme}>
81
- <TooltipProvider>
82
- <InputNumber
83
- data-testid="test-input"
84
- value={value}
85
- numberFormat={"Percent"}
86
- numberPrecision={2}
87
- format={formatPercent}
88
- onChange={setValue}
89
- />
90
- </TooltipProvider>
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 {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 {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 InputNumber from "rc-input-number";
8
- import {StepButton} from "./step-button";
9
- import {stopPropagation} from "@fibery/react/src/stop-propagation";
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 invalidCss = css``;
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 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
- }
21
+ const handleFocus = composeEventHandlers(onFocus, () => setIsEditing(true));
22
+ const handleBlur = composeEventHandlers(onBlur, () => setIsEditing(false));
28
23
 
29
- &.${invalidCss} {
30
- ${invalidInputStyle}
31
- }
32
- `;
33
-
34
- type NumberFormat = "Number" | "Money" | "Percent";
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
- export const NumberInlineInput = forwardRef<HTMLInputElement, Props>(
53
- (
54
- {
55
- value,
56
- numberFormat,
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
- ref
70
- ) => {
71
- const inputRef = useRef<HTMLInputElement | null>(null);
72
- const [inputContainerRef, setInputContainerRef] = useState<HTMLDivElement | null>(null);
38
+ [value, numberFormat, onChange]
39
+ );
73
40
 
74
- useImperativeHandle(ref, () => inputRef.current!, []);
41
+ const formattedValue = useMemo(() => {
42
+ return formatValue(value, isEditing, numberFormat, format);
43
+ }, [value, isEditing, numberFormat, format]);
75
44
 
76
- const handleBlur = useCallback(
77
- (e: React.FocusEvent<HTMLInputElement>) => {
78
- if (e.relatedTarget && inputContainerRef && !inputContainerRef.contains(e.relatedTarget)) {
79
- onBlur?.(e);
80
- }
81
- },
82
- [inputContainerRef, onBlur]
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
- const handleChange = useCallback(
86
- (value: string | number | null) => {
87
- if (onChange) {
88
- onChange(parseValue(value, numberFormat, numberPrecision));
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
- const handleStep = useCallback(
95
- (newValue: string | number, {type}: {type: "up" | "down"}) => {
96
- inputRef.current?.focus();
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
- const formattedValue = useMemo(() => {
108
- const isEditing = true;
109
- return formatValue(value, isEditing, numberFormat, format);
110
- }, [value, numberFormat, format]);
70
+ const handleBlur = useCallback(() => {
71
+ onBlur(isEmpty(value) ? null : value);
72
+ }, [onBlur, value]);
111
73
 
112
- return (
113
- <div ref={setInputContainerRef} className={cx(valueWrapperClassName, inputContainerCss, invalid && invalidCss)}>
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
+ };
@@ -1,5 +1,5 @@
1
1
  import {css, cx} from "@linaria/core";
2
- import _ from "lodash";
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
- {_.take(users, users.length === maxVisibleUsers ? maxVisibleUsers : maxVisibleUsers - 1).map(
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";