@fibery/ui-kit 1.36.0 → 1.36.1

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @fibery/ui-kit
2
2
 
3
+ ## 1.36.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [ec46f77]
8
+ - @fibery/helpers@1.3.2
9
+ - @fibery/react@1.4.3
10
+
3
11
  ## 1.36.0
4
12
 
5
13
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fibery/ui-kit",
3
- "version": "1.36.0",
3
+ "version": "1.36.1",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "dependencies": {
@@ -44,8 +44,8 @@
44
44
  "tabbable": "5.2.1",
45
45
  "ua-parser-js": "1.0.39",
46
46
  "@fibery/emoji-data": "2.6.1",
47
- "@fibery/helpers": "1.3.1",
48
- "@fibery/react": "1.4.2"
47
+ "@fibery/helpers": "1.3.2",
48
+ "@fibery/react": "1.4.3"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "react": "^18.2.0",
package/src/avatar.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import {css} from "@linaria/core";
2
2
  import _ from "lodash";
3
- import {CSSProperties, FC, memo, useCallback, useState} from "react";
3
+ import {CSSProperties, FC, memo, useCallback, useMemo, useState} from "react";
4
4
  import {getDarkenColor, getObjectColorMemoized, textStyles, themeVars} from "./design-system";
5
5
  import {FibermojiPlaceholder} from "./fibermoji-placeholder";
6
6
  import {getShiftStyle} from "./icons/get-shift-style";
@@ -82,6 +82,7 @@ const EMOJI_BY_BADGE = {
82
82
  vacation: "🌴",
83
83
  dayon: "💻",
84
84
  };
85
+
85
86
  function getAvatarUrl(url: string, size: number) {
86
87
  if (!url) {
87
88
  return url;
@@ -92,7 +93,17 @@ function getAvatarUrl(url: string, size: number) {
92
93
  return urlObject.toString();
93
94
  }
94
95
 
95
- interface AvatarImageProps {
96
+ /**
97
+ * This triggers a network request if the image is not cached.
98
+ * Avoid using in render or performance-critical code.
99
+ */
100
+ const isImageCached = (src: string): boolean => {
101
+ const image = new Image();
102
+ image.src = src;
103
+ return image.complete && image.naturalWidth !== 0;
104
+ };
105
+
106
+ type AvatarImageProps = {
96
107
  size: number;
97
108
  containerSize?: number;
98
109
  title?: string;
@@ -102,11 +113,8 @@ interface AvatarImageProps {
102
113
  backgroundColor?: string;
103
114
  disableNativeTitle?: boolean;
104
115
  placeholder?: React.ReactNode;
105
- }
106
- /**
107
- *
108
- * @type {React.NamedExoticComponent<{readonly badges?: *, readonly backgroundColor?: *, readonly size?: *, readonly avatarUrl?: *, readonly disableNativeTitle?: *, readonly title?: *, readonly radius?: *, readonly containerSize?: *}>}
109
- */
116
+ };
117
+
110
118
  export const AvatarImage: FC<AvatarImageProps> = memo(function AvatarImage({
111
119
  size,
112
120
  containerSize,
@@ -118,7 +126,13 @@ export const AvatarImage: FC<AvatarImageProps> = memo(function AvatarImage({
118
126
  disableNativeTitle = false,
119
127
  placeholder,
120
128
  }) {
121
- const [fetchState, setFetchState] = useState(":fetch-state/start");
129
+ const initialFetchState = useMemo(() => {
130
+ if (avatarUrl && isImageCached(avatarUrl)) {
131
+ return ":fetch-state/success";
132
+ }
133
+ return ":fetch-state/start";
134
+ }, [avatarUrl]);
135
+ const [fetchState, setFetchState] = useState(initialFetchState);
122
136
  const onError = useCallback(() => {
123
137
  setFetchState(":fetch-state/error");
124
138
  }, []);
@@ -193,12 +207,12 @@ const getAvatarPlaceholderStyle = ({
193
207
  borderRadius: radius ?? "50%",
194
208
  });
195
209
 
196
- interface AvatarPlaceholderProps {
210
+ type AvatarPlaceholderProps = {
197
211
  size: number;
198
212
  title?: string;
199
213
  radius?: number;
200
214
  backgroundColor?: string;
201
- }
215
+ };
202
216
 
203
217
  export const AvatarPlaceholder: FC<AvatarPlaceholderProps> = memo(function AvatarPlaceholder(props) {
204
218
  const features = useFeatures<{enableFibermojiAvatars: boolean}>();
@@ -237,10 +251,10 @@ const getNullUserAvatarStyle = ({size, containerSize}: {size: number; containerS
237
251
 
238
252
  const magicCoefficient = 4.5;
239
253
 
240
- interface NullUserAvatarProps {
254
+ type NullUserAvatarProps = {
241
255
  size: number;
242
256
  containerSize: number;
243
- }
257
+ };
244
258
 
245
259
  export const NullUserAvatar: FC<NullUserAvatarProps> = memo(function NullUserAvatar({size, containerSize}) {
246
260
  const dashSize = (Math.PI * size) / magicCoefficient / 12;
@@ -113,7 +113,7 @@ export const CommandMenuSeparator = styled(CommandSeparator)`
113
113
  margin: ${space.s4}px 0;
114
114
  `;
115
115
 
116
- const commandItemCss = css`
116
+ export const commandItemCss = css`
117
117
  display: flex;
118
118
  align-items: center;
119
119
  gap: ${space.s6}px;
@@ -135,17 +135,6 @@ const commandItemTitleCss = css`
135
135
  overflow: hidden;
136
136
  `;
137
137
 
138
- export const CommandExpressionSelectItem = forwardRef<
139
- React.ElementRef<typeof CommandItem>,
140
- React.ComponentPropsWithoutRef<typeof CommandItem>
141
- >(function CommandMenuItem({className, children, ...props}, ref) {
142
- return (
143
- <CommandItem ref={ref} className={cx(commandItemCss, className)} {...props}>
144
- {children}
145
- </CommandItem>
146
- );
147
- });
148
-
149
138
  export const CommandMenuItem = forwardRef<
150
139
  React.ElementRef<typeof CommandItem>,
151
140
  React.ComponentPropsWithoutRef<typeof CommandItem> & {
@@ -0,0 +1,349 @@
1
+ import {CommandItem} from "cmdk";
2
+ import React, {forwardRef, ReactNode, useCallback, useEffect, useMemo, useRef, useState} from "react";
3
+ import {css, cx} from "@linaria/core";
4
+ import {border, space, themeVars} from "../design-system";
5
+ import Back from "../icons/react/Back";
6
+ import {
7
+ commandItemCss,
8
+ CommandMenuEmpty,
9
+ CommandMenuGroup,
10
+ CommandMenuInput,
11
+ CommandMenuItem,
12
+ CommandMenuList,
13
+ CommandMenuRoot,
14
+ CommandMenuSeparator,
15
+ } from "./index";
16
+ import cn from "classnames";
17
+ import {Tooltip} from "../tooltip";
18
+ import {IconButton} from "../button/icon-button";
19
+ import ArrowRight from "../icons/react/ArrowRight";
20
+
21
+ const selectedItemClassName = css`
22
+ background-color: ${themeVars.colorBgListItemGeneralSelected};
23
+
24
+ &[data-selected="true"] {
25
+ background-color: ${themeVars.colorBgListItemGeneralSelectedHover};
26
+ }
27
+ `;
28
+
29
+ const arrowWrapperClassName = css`
30
+ flex-grow: 0;
31
+ `;
32
+
33
+ export type NestedMenuItem<T> = {
34
+ key: string;
35
+ title?: string;
36
+ icon?: ReactNode;
37
+ value: T;
38
+ groupItems?: Array<NestedMenuItem<T>>;
39
+ filterPlaceholder?: string;
40
+ emptyMessage?: string;
41
+ calculateNestedItems?: () => Array<NestedMenuItem<T>>;
42
+ kind?: "separator" | "group";
43
+ };
44
+
45
+ export type NestedMenuStack<T> = Array<{
46
+ item: NestedMenuItem<T>;
47
+ nestedItems: NestedMenuItem<T>[];
48
+ }>;
49
+
50
+ type Props<T> = {
51
+ defaultStack?: NestedMenuStack<T>;
52
+ defaultValue?: string;
53
+ rootItems: Array<NestedMenuItem<T>>;
54
+ onChange: (item: NestedMenuItem<T>) => void;
55
+ filterPlaceholder: string;
56
+ emptyMessage: string;
57
+ getArrowTooltipMessage: (item: NestedMenuItem<T>) => string;
58
+ maxDepthLevel?: number;
59
+ };
60
+
61
+ const NestedCommandMenuItem = forwardRef<
62
+ React.ElementRef<typeof CommandItem>,
63
+ React.ComponentPropsWithoutRef<typeof CommandItem>
64
+ >(function CommandMenuItem({className, children, ...props}, ref) {
65
+ return (
66
+ <CommandItem ref={ref} className={cx(commandItemCss, className)} {...props}>
67
+ {children}
68
+ </CommandItem>
69
+ );
70
+ });
71
+
72
+ function isItemMatchFilter<T>(item: NestedMenuItem<T>, lowerCaseFilterValue: string) {
73
+ return item.title?.toLowerCase()?.includes(lowerCaseFilterValue);
74
+ }
75
+
76
+ export function NestedCommandMenu<T>({
77
+ defaultStack,
78
+ defaultValue,
79
+ rootItems,
80
+ onChange,
81
+ filterPlaceholder,
82
+ emptyMessage,
83
+ getArrowTooltipMessage,
84
+ maxDepthLevel = Number.MAX_VALUE,
85
+ }: Props<T>) {
86
+ const [stack, setStack] = React.useState<NestedMenuStack<T>>(defaultStack || []);
87
+ const currentStackItem = stack[stack.length - 1];
88
+
89
+ const currentItems = currentStackItem?.nestedItems || rootItems;
90
+
91
+ const onGoToRelation = useCallback(
92
+ (key: string) => {
93
+ const item = currentItems.find((item) => item.key === key)!;
94
+ setStack((items) => [...items, {item, nestedItems: item.calculateNestedItems?.() || []}]);
95
+ setFilter("");
96
+ },
97
+ [currentItems]
98
+ );
99
+
100
+ const onCmdkItemSelect = useCallback(
101
+ (item: NestedMenuItem<T>) => {
102
+ onChange(item);
103
+ },
104
+ [onChange]
105
+ );
106
+
107
+ const [filter, setFilter] = useState("");
108
+
109
+ const filteredOptions = useMemo(() => {
110
+ const lowerCaseFilterValue = filter.toLowerCase();
111
+ if (!filter) {
112
+ return currentItems;
113
+ }
114
+
115
+ return currentItems
116
+ .map((item) => {
117
+ if (item.kind === "group") {
118
+ const groupItems = (item.groupItems || []).filter((groupItem) =>
119
+ isItemMatchFilter(groupItem, lowerCaseFilterValue)
120
+ );
121
+
122
+ return groupItems.length > 0 ? {...item, groupItems} : null;
123
+ }
124
+
125
+ return isItemMatchFilter(item, lowerCaseFilterValue) ? item : null;
126
+ })
127
+ .filter(Boolean) as Array<NestedMenuItem<T>>;
128
+ }, [filter, currentItems]);
129
+
130
+ const inputRef = useRef<HTMLInputElement>(null);
131
+
132
+ useEffect(() => {
133
+ setTimeout(() => {
134
+ inputRef.current?.focus();
135
+ }, 0);
136
+ }, [stack]);
137
+
138
+ const menuListRef = useRef<HTMLDivElement>(null);
139
+ const scrollToTop = useCallback(() => {
140
+ menuListRef.current?.scrollTo({top: 0, behavior: "instant"});
141
+ }, []);
142
+ useEffect(() => {
143
+ scrollToTop();
144
+ }, [stack, scrollToTop]);
145
+
146
+ const [value, setValue] = React.useState(defaultValue);
147
+
148
+ return (
149
+ <>
150
+ {stack.length > 0 && (
151
+ <>
152
+ <div
153
+ className={css`
154
+ cursor: pointer;
155
+
156
+ &:hover {
157
+ background-color: ${themeVars.colorBgActionsMenuItemHover};
158
+ }
159
+
160
+ display: flex;
161
+ padding: ${space.s6}px ${space.s8}px;
162
+ border-radius: ${border.radius6}px;
163
+ margin: 0 ${space.s4}px;
164
+ align-items: center;
165
+ gap: ${space.s8}px;
166
+ `}
167
+ onClick={() => {
168
+ setStack((relations) => relations.slice(0, -1));
169
+ setFilter("");
170
+ }}
171
+ >
172
+ <div>
173
+ <Back iconSize={16} aria-hidden />
174
+ </div>
175
+ <div>
176
+ Back
177
+ {stack.length > 1 ? ` to ${stack[stack.length - 2].item.title}` : ""}
178
+ </div>
179
+ </div>
180
+ <div
181
+ className={css`
182
+ background-color: ${themeVars.separatorColor};
183
+ height: 1px;
184
+ margin-top: ${space.s4}px;
185
+ `}
186
+ />
187
+ </>
188
+ )}
189
+
190
+ <CommandMenuRoot
191
+ shouldFilter={false}
192
+ value={value}
193
+ onValueChange={setValue}
194
+ onKeyDown={(e) => {
195
+ if (e.key === "Escape" || (e.key === "Backspace" && !filter && stack.length > 0)) {
196
+ e.preventDefault();
197
+ setStack((relations) => relations.slice(0, -1));
198
+ } else if (e.key === "ArrowRight") {
199
+ const currentItem = currentItems.find((item) => item.key === value);
200
+ if (currentItem && currentItem.calculateNestedItems) {
201
+ e.preventDefault();
202
+ onGoToRelation(currentItem.key);
203
+ }
204
+ } else if (e.key === "ArrowLeft" && stack.length > 0) {
205
+ e.preventDefault();
206
+ setStack((relations) => relations.slice(0, -1));
207
+ }
208
+ }}
209
+ >
210
+ <CommandMenuInput
211
+ ref={inputRef}
212
+ value={filter}
213
+ onValueChange={setFilter}
214
+ placeholder={currentStackItem?.item?.filterPlaceholder || filterPlaceholder}
215
+ />
216
+ <CommandMenuList
217
+ ref={menuListRef}
218
+ className={css`
219
+ && {
220
+ transition: none;
221
+ max-height: 300px;
222
+ }
223
+ `}
224
+ >
225
+ <CommandMenuEmpty>{currentStackItem?.item?.emptyMessage || emptyMessage}</CommandMenuEmpty>
226
+ {renderLevel({
227
+ options: filteredOptions,
228
+ stack,
229
+ maxDepthLevel,
230
+ onCmdkItemSelect,
231
+ onGoToRelation,
232
+ getArrowTooltipMessage,
233
+ defaultValue,
234
+ })}
235
+ </CommandMenuList>
236
+ </CommandMenuRoot>
237
+ </>
238
+ );
239
+ }
240
+
241
+ function renderLevel<T>({
242
+ options,
243
+ defaultValue,
244
+ stack,
245
+ maxDepthLevel,
246
+ onCmdkItemSelect,
247
+ onGoToRelation,
248
+ getArrowTooltipMessage,
249
+ }: {
250
+ options: Array<NestedMenuItem<T>>;
251
+ defaultValue?: string;
252
+ stack: NestedMenuStack<T>;
253
+ maxDepthLevel: number;
254
+ onCmdkItemSelect: (item: NestedMenuItem<T>) => void;
255
+ onGoToRelation: (key: string) => void;
256
+ getArrowTooltipMessage: (item: NestedMenuItem<T>) => string;
257
+ }) {
258
+ return options.map((item) => {
259
+ if (item.kind === "separator") {
260
+ return <CommandMenuSeparator key={item.key} />;
261
+ }
262
+
263
+ if (item.kind === "group") {
264
+ const nestedItems = item.groupItems || [];
265
+ if (nestedItems.length > 0) {
266
+ return (
267
+ <CommandMenuGroup heading={item.title} key={item.key}>
268
+ {renderLevel({
269
+ options: nestedItems,
270
+ defaultValue,
271
+ stack,
272
+ maxDepthLevel,
273
+ onCmdkItemSelect,
274
+ onGoToRelation,
275
+ getArrowTooltipMessage,
276
+ })}
277
+ </CommandMenuGroup>
278
+ );
279
+ }
280
+
281
+ return null;
282
+ }
283
+
284
+ const isSelected = item.key === defaultValue;
285
+ if (item.calculateNestedItems && stack.length < maxDepthLevel) {
286
+ return (
287
+ <NestedCommandMenuItem
288
+ key={item.key}
289
+ value={item.key}
290
+ onSelect={() => onCmdkItemSelect(item)}
291
+ asChild
292
+ className={cn(
293
+ css`
294
+ padding-right: 0;
295
+ `,
296
+ isSelected ? selectedItemClassName : undefined
297
+ )}
298
+ >
299
+ <div
300
+ className={css`
301
+ display: flex;
302
+ align-items: center;
303
+ justify-content: space-between;
304
+ gap: ${space.s6}px;
305
+
306
+ &[data-selected="true"]:has(.${arrowWrapperClassName}:hover) {
307
+ background-color: transparent;
308
+ }
309
+ `}
310
+ >
311
+ {item.icon}
312
+ <div
313
+ className={css`
314
+ flex: 1;
315
+ `}
316
+ >
317
+ {item.title}
318
+ </div>
319
+ <div className={arrowWrapperClassName}>
320
+ <Tooltip title={getArrowTooltipMessage(item)}>
321
+ <IconButton
322
+ size={"medium"}
323
+ onClick={(e) => {
324
+ e.stopPropagation();
325
+ onGoToRelation(item.key);
326
+ }}
327
+ >
328
+ <ArrowRight color={themeVars.iconColor} containerSize={10} aria-hidden />
329
+ </IconButton>
330
+ </Tooltip>
331
+ </div>
332
+ </div>
333
+ </NestedCommandMenuItem>
334
+ );
335
+ } else {
336
+ return (
337
+ <CommandMenuItem
338
+ key={item.key}
339
+ value={item.key}
340
+ onSelect={() => onCmdkItemSelect(item)}
341
+ icon={item.icon}
342
+ className={isSelected ? selectedItemClassName : undefined}
343
+ >
344
+ {item.title}
345
+ </CommandMenuItem>
346
+ );
347
+ }
348
+ });
349
+ }
@@ -17,6 +17,7 @@ const unitOptions = [
17
17
  {value: "day", label: "days"},
18
18
  {value: "week", label: "weeks"},
19
19
  {value: "month", label: "months"},
20
+ {value: "quarter", label: "quarters"},
20
21
  {value: "year", label: "years"},
21
22
  ];
22
23
 
@@ -25,12 +26,6 @@ const isBeforeNowOptions = [
25
26
  {value: "from now", label: "from now"},
26
27
  ];
27
28
 
28
- const blurOnEnter = (e: $TSFixMe) => {
29
- if (e.key === "Enter") {
30
- e.target.blur();
31
- }
32
- };
33
-
34
29
  export const RelativeDatePicker = (props: RelativeDatePickerProps) => {
35
30
  const {value, onChange, relativeDateToExactDate} = props;
36
31
 
@@ -45,14 +40,30 @@ export const RelativeDatePicker = (props: RelativeDatePickerProps) => {
45
40
 
46
41
  const [amount, setAmount] = useState(valueWithDefault.amount);
47
42
  const onAmountChange = useCallback((newAmount: $TSFixMe) => {
48
- if (newAmount === null || newAmount < 0) {
43
+ if (newAmount === null) {
44
+ return;
45
+ }
46
+ if (newAmount < 0) {
49
47
  setAmount(0);
50
48
  } else {
51
49
  setAmount(Math.floor(newAmount));
52
50
  }
53
51
  }, []);
52
+
53
+ const onKeyDown = useCallback(
54
+ (e: $TSFixMe) => {
55
+ if (e.key === "Enter") {
56
+ onChange({...valueWithDefault, amount});
57
+ e.target.blur();
58
+ }
59
+ },
60
+ [amount, onChange, valueWithDefault]
61
+ );
62
+
54
63
  const handleBlur = useCallback(() => {
55
- onChange({...valueWithDefault, amount});
64
+ if (amount !== valueWithDefault.amount) {
65
+ onChange({...valueWithDefault, amount});
66
+ }
56
67
  }, [onChange, amount, valueWithDefault]);
57
68
 
58
69
  const onIsBeforeNowChange = useCallback(
@@ -85,7 +96,7 @@ export const RelativeDatePicker = (props: RelativeDatePickerProps) => {
85
96
  style={{width: "56px"}}
86
97
  onBlur={handleBlur}
87
98
  onChange={onAmountChange}
88
- onKeyDown={blurOnEnter}
99
+ onKeyDown={onKeyDown}
89
100
  />
90
101
  </div>
91
102
  <div
@@ -0,0 +1,8 @@
1
+
2
+ // This icon file is generated automatically.
3
+
4
+ import { IconDefinition } from '../types';
5
+
6
+ const DateRange: IconDefinition = {"icon":{"type":"root","children":[{"type":"element","tagName":"svg","properties":{"viewBox":"0 0 20 20"},"children":[{"type":"element","tagName":"path","properties":{"fillRule":"evenodd","clipRule":"evenodd","d":"M14 3.6h1.6A2.4 2.4 0 0 1 18 6v9.6a2.4 2.4 0 0 1-2.4 2.4H4.4A2.4 2.4 0 0 1 2 15.6V6a2.4 2.4 0 0 1 2.4-2.4H6v-.8a.8.8 0 0 1 1.6 0v.8h4.8v-.8a.8.8 0 0 1 1.6 0v.8Zm2.166 12.566a.8.8 0 0 0 .234-.566V6a.8.8 0 0 0-.8-.8H14V6a.8.8 0 0 1-1.6 0v-.8H7.6V6A.8.8 0 0 1 6 6v-.8H4.4a.8.8 0 0 0-.8.8v9.6a.8.8 0 0 0 .8.8h11.2a.8.8 0 0 0 .566-.234ZM8.78 8.22a.75.75 0 0 1 0 1.06L7.56 10.5h4.88l-1.22-1.22a.75.75 0 0 1 1.06-1.06l2.5 2.5a.75.75 0 0 1 0 1.06l-2.5 2.5a.75.75 0 1 1-1.06-1.06L12.44 12H7.56l1.22 1.22a.75.75 0 1 1-1.06 1.06l-2.5-2.5a.75.75 0 0 1 0-1.06l2.5-2.5a.75.75 0 0 1 1.06 0Z"},"children":[]}],"metadata":""}]},"name":"date-range"};
7
+
8
+ export default DateRange;
@@ -0,0 +1,8 @@
1
+
2
+ // This icon file is generated automatically.
3
+
4
+ import { IconDefinition } from '../types';
5
+
6
+ const ReadOnly: IconDefinition = {"icon":{"type":"root","children":[{"type":"element","tagName":"svg","properties":{"viewBox":"0 0 20 20"},"children":[{"type":"element","tagName":"path","properties":{"fillRule":"evenodd","clipRule":"evenodd","d":"M2.22 2.22a.75.75 0 0 1 1.06 0l4.845 4.844 3.47-3.47a3.402 3.402 0 1 1 4.81 4.811l-3.47 3.47 4.845 4.845a.75.75 0 1 1-1.06 1.06L2.22 3.28a.75.75 0 0 1 0-1.06Zm11.683 6.567-2.028 2.027-2.69-2.689 2.028-2.028 2.69 2.69Zm1.442-4.132a1.902 1.902 0 0 0-2.69 0l-.558.558 2.69 2.69.558-.558a1.902 1.902 0 0 0 0-2.69ZM6.534 10.777a.75.75 0 0 0-1.06-1.061L2.72 12.47a.75.75 0 0 0-.22.53v3.5a1 1 0 0 0 1 1H7a.75.75 0 0 0 .53-.22l2.754-2.753a.75.75 0 0 0-1.06-1.061L6.688 16H4v-2.69l2.534-2.533Z"},"children":[]}],"metadata":""}]},"name":"read-only"};
7
+
8
+ export default ReadOnly;
@@ -84,6 +84,7 @@ export { default as CrossCircle } from './CrossCircle';
84
84
  export { default as DatabaseOff } from './DatabaseOff';
85
85
  export { default as DatabaseStroke } from './DatabaseStroke';
86
86
  export { default as Database } from './Database';
87
+ export { default as DateRange } from './DateRange';
87
88
  export { default as Delete } from './Delete';
88
89
  export { default as Demo } from './Demo';
89
90
  export { default as Dependency } from './Dependency';
@@ -212,6 +213,7 @@ export { default as PresentPlay } from './PresentPlay';
212
213
  export { default as PresentStop } from './PresentStop';
213
214
  export { default as PrivateItems } from './PrivateItems';
214
215
  export { default as Question } from './Question';
216
+ export { default as ReadOnly } from './ReadOnly';
215
217
  export { default as Refresh } from './Refresh';
216
218
  export { default as RemovePeople } from './RemovePeople';
217
219
  export { default as Remove } from './Remove';
@@ -0,0 +1,13 @@
1
+ // This icon file is generated automatically.
2
+
3
+ import {forwardRef} from 'react';
4
+ import DateRangeSvg from '../ast/DateRange';
5
+ import { Icon } from '../Icon';
6
+ import { IconBaseProps } from '../types';
7
+
8
+ const DateRange = forwardRef<SVGSVGElement, IconBaseProps>(function DateRange(
9
+ props: IconBaseProps,
10
+ ref: React.Ref<SVGSVGElement>
11
+ ) {return <Icon {...props} className={props.className} ref={ref} icon={DateRangeSvg} />});
12
+
13
+ export default DateRange;
@@ -0,0 +1,13 @@
1
+ // This icon file is generated automatically.
2
+
3
+ import {forwardRef} from 'react';
4
+ import ReadOnlySvg from '../ast/ReadOnly';
5
+ import { Icon } from '../Icon';
6
+ import { IconBaseProps } from '../types';
7
+
8
+ const ReadOnly = forwardRef<SVGSVGElement, IconBaseProps>(function ReadOnly(
9
+ props: IconBaseProps,
10
+ ref: React.Ref<SVGSVGElement>
11
+ ) {return <Icon {...props} className={props.className} ref={ref} icon={ReadOnlySvg} />});
12
+
13
+ export default ReadOnly;
@@ -84,6 +84,7 @@ export { default as CrossCircle } from './CrossCircle';
84
84
  export { default as DatabaseOff } from './DatabaseOff';
85
85
  export { default as DatabaseStroke } from './DatabaseStroke';
86
86
  export { default as Database } from './Database';
87
+ export { default as DateRange } from './DateRange';
87
88
  export { default as Delete } from './Delete';
88
89
  export { default as Demo } from './Demo';
89
90
  export { default as Dependency } from './Dependency';
@@ -212,6 +213,7 @@ export { default as PresentPlay } from './PresentPlay';
212
213
  export { default as PresentStop } from './PresentStop';
213
214
  export { default as PrivateItems } from './PrivateItems';
214
215
  export { default as Question } from './Question';
216
+ export { default as ReadOnly } from './ReadOnly';
215
217
  export { default as Refresh } from './Refresh';
216
218
  export { default as RemovePeople } from './RemovePeople';
217
219
  export { default as Remove } from './Remove';
@@ -0,0 +1,4 @@
1
+ <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd"
3
+ d="M14 3.6H15.6C16.2365 3.6 16.847 3.85286 17.2971 4.30294C17.7471 4.75303 18 5.36348 18 6V15.6C18 16.2365 17.7471 16.847 17.2971 17.2971C16.847 17.7471 16.2365 18 15.6 18H4.4C3.76348 18 3.15303 17.7471 2.70294 17.2971C2.25286 16.847 2 16.2365 2 15.6V6C2 5.36348 2.25286 4.75303 2.70294 4.30294C3.15303 3.85286 3.76348 3.6 4.4 3.6H6V2.8C6 2.58783 6.08429 2.38434 6.23431 2.23431C6.38434 2.08429 6.58783 2 6.8 2C7.01217 2 7.21566 2.08429 7.36569 2.23431C7.51571 2.38434 7.6 2.58783 7.6 2.8V3.6H12.4V2.8C12.4 2.58783 12.4843 2.38434 12.6343 2.23431C12.7843 2.08429 12.9878 2 13.2 2C13.4122 2 13.6157 2.08429 13.7657 2.23431C13.9157 2.38434 14 2.58783 14 2.8V3.6ZM16.1657 16.1657C16.3157 16.0157 16.4 15.8122 16.4 15.6V6C16.4 5.78783 16.3157 5.58434 16.1657 5.43431C16.0157 5.28429 15.8122 5.2 15.6 5.2H14V6C14 6.21217 13.9157 6.41566 13.7657 6.56569C13.6157 6.71571 13.4122 6.8 13.2 6.8C12.9878 6.8 12.7843 6.71571 12.6343 6.56569C12.4843 6.41566 12.4 6.21217 12.4 6V5.2H7.6V6C7.6 6.21217 7.51571 6.41566 7.36569 6.56569C7.21566 6.71571 7.01217 6.8 6.8 6.8C6.58783 6.8 6.38434 6.71571 6.23431 6.56569C6.08429 6.41566 6 6.21217 6 6V5.2H4.4C4.18783 5.2 3.98434 5.28429 3.83431 5.43431C3.68429 5.58434 3.6 5.78783 3.6 6V15.6C3.6 15.8122 3.68429 16.0157 3.83431 16.1657C3.98434 16.3157 4.18783 16.4 4.4 16.4H15.6C15.8122 16.4 16.0157 16.3157 16.1657 16.1657ZM8.78033 8.21967C9.07322 8.51256 9.07322 8.98744 8.78033 9.28033L7.56066 10.5H12.4393L11.2197 9.28033C10.9268 8.98744 10.9268 8.51256 11.2197 8.21967C11.5126 7.92678 11.9874 7.92678 12.2803 8.21967L14.7803 10.7197C15.0732 11.0126 15.0732 11.4874 14.7803 11.7803L12.2803 14.2803C11.9874 14.5732 11.5126 14.5732 11.2197 14.2803C10.9268 13.9874 10.9268 13.5126 11.2197 13.2197L12.4393 12H7.56066L8.78033 13.2197C9.07322 13.5126 9.07322 13.9874 8.78033 14.2803C8.48744 14.5732 8.01256 14.5732 7.71967 14.2803L5.21967 11.7803C4.92678 11.4874 4.92678 11.0126 5.21967 10.7197L7.71967 8.21967C8.01256 7.92678 8.48744 7.92678 8.78033 8.21967Z" />
4
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd"
3
+ d="M2.21967 2.21967C2.51256 1.92678 2.98744 1.92678 3.28033 2.21967L8.125 7.06434L11.5947 3.59468C12.9231 2.26625 15.0769 2.26625 16.4053 3.59468C17.7338 4.92311 17.7338 7.07691 16.4053 8.40534L12.9357 11.875L17.7803 16.7197C18.0732 17.0126 18.0732 17.4874 17.7803 17.7803C17.4874 18.0732 17.0126 18.0732 16.7197 17.7803L2.21967 3.28033C1.92678 2.98744 1.92678 2.51256 2.21967 2.21967ZM13.9027 8.78662L11.875 10.8143L9.18566 8.125L11.2134 6.09728L13.9027 8.78662ZM15.3447 4.65534C14.602 3.9127 13.398 3.9127 12.6553 4.65534L12.0973 5.21339L14.7866 7.90273L15.3447 7.34468C16.0873 6.60204 16.0873 5.39798 15.3447 4.65534Z" />
4
+ <path fill-rule="evenodd" clip-rule="evenodd"
5
+ d="M6.53401 10.7767C6.82691 10.4838 6.82691 10.0089 6.53401 9.71599C6.24112 9.4231 5.76625 9.4231 5.47335 9.71599L2.71967 12.4697C2.57902 12.6103 2.5 12.8011 2.5 13V16.5C2.5 17.0523 2.94772 17.5 3.5 17.5H7C7.19891 17.5 7.38968 17.421 7.53033 17.2803L10.284 14.5267C10.5769 14.2338 10.5769 13.7589 10.284 13.466C9.99112 13.1731 9.51625 13.1731 9.22335 13.466L6.68934 16H4V13.3107L6.53401 10.7767Z" />
6
+ </svg>
package/src/loaders.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import {css, cx} from "@linaria/core";
2
2
  import {styled} from "@linaria/react";
3
- import {ReactNode, memo} from "react";
3
+ import {ReactNode, memo, useMemo} from "react";
4
4
  import {IconBaseProps} from "./icons/types";
5
5
  import {border, layout, space, textStyles, themeVars} from "./design-system";
6
6
  import {getShiftStyle} from "./icons/get-shift-style";
@@ -104,35 +104,40 @@ const ContainerSpinner = styled.div<{containerSize: number}>`
104
104
  line-height: ${(props) => props.containerSize}px;
105
105
  `;
106
106
 
107
- type SpinnerProps = {
107
+ type Props = {
108
108
  size?: number;
109
109
  containerSize?: number;
110
110
  width?: number;
111
111
  color?: string;
112
112
  backgroundColor?: string;
113
113
  };
114
- export const Spinner = memo<SpinnerProps>(function Spinner({
115
- size = 20,
116
- containerSize = 15,
117
- width = 2,
118
- color = "#000000",
119
- backgroundColor = "#FFFFFF",
120
- }) {
121
- const spinnerStyle = {
122
- width: size,
123
- height: size,
124
- ...getShiftStyle({elementSize: size, containerSize}),
125
- borderWidth: width,
126
- borderColor: backgroundColor,
127
- borderTopColor: color,
128
- };
129
114
 
130
- return (
131
- <ContainerSpinner containerSize={containerSize}>
132
- <div style={spinnerStyle} className={spinner} />
133
- </ContainerSpinner>
134
- );
135
- });
115
+ export const Spinner = memo<Props>(
116
+ ({
117
+ size = 20,
118
+ containerSize = 15,
119
+ width = 2,
120
+ color = themeVars.iconColor,
121
+ backgroundColor = themeVars.badgeBgColor,
122
+ }) => {
123
+ const style = useMemo(() => {
124
+ return {
125
+ width: size,
126
+ height: size,
127
+ ...getShiftStyle({elementSize: size, containerSize}),
128
+ borderWidth: width,
129
+ borderColor: backgroundColor,
130
+ borderTopColor: color,
131
+ };
132
+ }, [backgroundColor, color, containerSize, size, width]);
133
+
134
+ return (
135
+ <ContainerSpinner containerSize={containerSize}>
136
+ <div style={style} className={spinner} />
137
+ </ContainerSpinner>
138
+ );
139
+ }
140
+ );
136
141
 
137
142
  export const LoadingTreeMenuItem = ({
138
143
  color,
@@ -1,6 +1,6 @@
1
1
  import {textStyles, themeVars} from "../../src/design-system";
2
2
  import {css} from "@linaria/core";
3
- import {useCallback, useMemo, useRef, useState, useImperativeHandle, forwardRef} from "react";
3
+ import {ComponentProps, forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState} from "react";
4
4
  import {formatValue, getStep, parseValue} from "./index";
5
5
  import _ from "lodash";
6
6
  import {add, sub} from "./decimal";
@@ -10,9 +10,9 @@ import {stopPropagation} from "@fibery/react/src/stop-propagation";
10
10
  import cx from "classnames";
11
11
  import {invalidInputStyle} from "./edit-unit-styles";
12
12
 
13
- const invalidClassName = css``;
13
+ const invalidCss = css``;
14
14
 
15
- export const inputContainerClassName = css`
15
+ const inputContainerCss = css`
16
16
  position: relative;
17
17
  input {
18
18
  background-color: ${themeVars.transparent};
@@ -26,31 +26,30 @@ export const inputContainerClassName = css`
26
26
  }
27
27
  }
28
28
 
29
- &.${invalidClassName} {
29
+ &.${invalidCss} {
30
30
  ${invalidInputStyle}
31
31
  }
32
32
  `;
33
33
 
34
- export const NumberInlineInput = forwardRef<
35
- HTMLInputElement,
36
- {
37
- value: string | number | null;
38
- numberFormat: "Number" | "Money" | "Percent";
39
- format?: (value: string | number) => string;
40
- numberPrecision: number;
41
- onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
42
- onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
43
- onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
44
- onChange: (value: string | number) => void;
45
- placeholder: string;
46
- autoFocus: boolean;
47
- style: object;
48
- width?: number;
49
- valueWrapperClassName?: string;
50
- decimalSeparator?: string;
51
- invalid?: boolean;
52
- }
53
- >(
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
+ };
51
+
52
+ export const NumberInlineInput = forwardRef<HTMLInputElement, Props>(
54
53
  (
55
54
  {
56
55
  value,
@@ -69,23 +68,15 @@ export const NumberInlineInput = forwardRef<
69
68
  },
70
69
  ref
71
70
  ) => {
72
- const isEditing = true;
73
- const handleFocus: React.FocusEventHandler<HTMLInputElement> = useCallback(
74
- (e) => {
75
- if (onFocus) {
76
- onFocus(e);
77
- }
78
- },
79
- [onFocus]
80
- );
81
-
82
71
  const inputRef = useRef<HTMLInputElement | null>(null);
83
72
  const [inputContainerRef, setInputContainerRef] = useState<HTMLDivElement | null>(null);
84
73
 
85
- const handleBlur: React.FocusEventHandler<HTMLInputElement> = useCallback(
86
- (e) => {
87
- if (onBlur && e.relatedTarget && inputContainerRef && !inputContainerRef.contains(e.relatedTarget)) {
88
- onBlur(e);
74
+ useImperativeHandle(ref, () => inputRef.current!, []);
75
+
76
+ const handleBlur = useCallback(
77
+ (e: React.FocusEvent<HTMLInputElement>) => {
78
+ if (e.relatedTarget && inputContainerRef && !inputContainerRef.contains(e.relatedTarget)) {
79
+ onBlur?.(e);
89
80
  }
90
81
  },
91
82
  [inputContainerRef, onBlur]
@@ -101,14 +92,7 @@ export const NumberInlineInput = forwardRef<
101
92
  );
102
93
 
103
94
  const handleStep = useCallback(
104
- (
105
- newValue: string | number,
106
- {
107
- type,
108
- }: {
109
- type: "up" | "down";
110
- }
111
- ) => {
95
+ (newValue: string | number, {type}: {type: "up" | "down"}) => {
112
96
  inputRef.current?.focus();
113
97
  const step = getStep(numberFormat);
114
98
  if (type === "up") {
@@ -119,29 +103,26 @@ export const NumberInlineInput = forwardRef<
119
103
  },
120
104
  [numberFormat, onChange, value]
121
105
  );
122
- useImperativeHandle(ref, () => inputRef.current!, []);
123
106
 
124
107
  const formattedValue = useMemo(() => {
108
+ const isEditing = true;
125
109
  return formatValue(value, isEditing, numberFormat, format);
126
- }, [value, isEditing, numberFormat, format]);
110
+ }, [value, numberFormat, format]);
127
111
 
128
112
  return (
129
- <div
130
- ref={setInputContainerRef}
131
- className={cx(valueWrapperClassName, inputContainerClassName, invalid && invalidClassName)}
132
- >
113
+ <div ref={setInputContainerRef} className={cx(valueWrapperClassName, inputContainerCss, invalid && invalidCss)}>
133
114
  <InputNumber
134
115
  autoFocus={autoFocus}
135
116
  ref={inputRef}
136
117
  value={formattedValue}
137
- onFocus={handleFocus}
138
- onBlur={handleBlur}
139
- onChange={handleChange}
140
- onKeyDown={onKeyDown}
141
118
  type="text"
142
119
  placeholder={placeholder}
143
120
  upHandler={<StepButton type={"up"} />}
144
121
  downHandler={<StepButton type={"down"} />}
122
+ onFocus={onFocus}
123
+ onBlur={handleBlur}
124
+ onChange={handleChange}
125
+ onKeyDown={onKeyDown}
145
126
  onStep={handleStep}
146
127
  onClick={stopPropagation}
147
128
  {...rest}
@@ -1,5 +1,5 @@
1
1
  import {css} from "@linaria/core";
2
- import {GroupHeadingProps, components} from "react-select";
2
+ import {components, GroupHeadingProps} from "react-select";
3
3
  import {layout, space, textStyles, themeVars} from "../../design-system";
4
4
  import {TooltipIfOverflown} from "../../tooltip-if-overflown";
5
5
  import {GroupBase} from "../index";
@@ -47,6 +47,7 @@ const GroupDivider = () => (
47
47
  <div className={groupDividerStyle}></div>
48
48
  </div>
49
49
  );
50
+
50
51
  export function GroupHeading<
51
52
  Option,
52
53
  IsMulti extends boolean = false,
@@ -77,3 +78,25 @@ export function GroupHeading<
77
78
  </components.GroupHeading>
78
79
  );
79
80
  }
81
+
82
+ // probably can be united with "GroupHeading" above.
83
+ export function GroupSeparator<
84
+ Option,
85
+ IsMulti extends boolean = false,
86
+ Group extends GroupBase<Option> = GroupBase<Option>
87
+ >({...props}: GroupHeadingProps<Option, IsMulti, Group>) {
88
+ // Hide separators when searching
89
+ if (!props.data.separator || props.selectProps.inputValue) {
90
+ return null;
91
+ }
92
+
93
+ return (
94
+ <hr
95
+ style={{margin: `${((props.data.separatorMargin || space.s8) - 1) / 2}px 0`}}
96
+ className={css`
97
+ border: none;
98
+ border-bottom: 1px solid ${themeVars.separatorColor};
99
+ `}
100
+ />
101
+ );
102
+ }
@@ -1,11 +1,11 @@
1
- import {OptionProps, components} from "react-select";
2
- import {GroupBase} from "../index";
3
1
  import {css} from "@linaria/core";
4
- import {layout, space} from "../../design-system";
5
- import {ListRowContent, ListRowSurface} from "../../lists/list-row-surface";
6
2
  import cn from "classnames";
7
3
  import {ComponentProps, useMemo} from "react";
4
+ import {components, OptionProps} from "react-select";
5
+ import {layout, space} from "../../design-system";
6
+ import {ListRowContent, ListRowSurface} from "../../lists/list-row-surface";
8
7
  import {TooltipIfOverflown} from "../../tooltip-if-overflown";
8
+ import {GroupBase} from "../index";
9
9
  import {isPureTextChildren} from "../util";
10
10
 
11
11
  export const OptionNulledVitualizedStyles = {
@@ -22,16 +22,22 @@ export const OptionNulledStyles = {
22
22
  lineHeight: "",
23
23
  color: "",
24
24
  };
25
+
25
26
  const OptionRootClass = css`
26
- padding: 0 ${space.s6}px;
27
- display: flex;
28
- align-items: stretch;
29
- justify-content: stretch;
30
- cursor: pointer;
31
- max-width: 100%;
27
+ :where(&) {
28
+ padding: 0 ${space.s6}px;
29
+ display: flex;
30
+ align-items: stretch;
31
+ justify-content: stretch;
32
+ cursor: pointer;
33
+ max-width: 100%;
34
+ }
32
35
  `;
33
36
  const OptionWithTextContent = css`
34
- padding-left: ${space.s6}px;
37
+ :where(&) {
38
+ padding-left: ${space.s6}px;
39
+ padding-right: ${space.s6}px;
40
+ }
35
41
  `;
36
42
 
37
43
  /**
@@ -50,7 +50,7 @@ const popupContainerClassName = css`
50
50
  align-items: flex-end;
51
51
  }
52
52
  `;
53
- const popupClassName = css`
53
+ export const popupClassName = css`
54
54
  min-width: 320px;
55
55
  max-width: 440px;
56
56
  `;
@@ -1,11 +1,12 @@
1
1
  import {GroupBase as ReactSelectGroupBase, OptionsOrGroups, SelectInstance} from "react-select";
2
2
  import {isObject} from "lodash";
3
3
  import {Children, createContext, ReactNode, RefObject} from "react";
4
+
4
5
  /**
5
6
  * API used by ReactSelect internal partials (at least VirtualizedMenuList).
6
7
  */
7
8
  export const ReactSelectRefContext = createContext<RefObject<SelectInstance>>({current: null});
8
- export type GroupBase<T> = ReactSelectGroupBase<T> & {separator?: boolean};
9
+ export type GroupBase<T> = ReactSelectGroupBase<T> & {separator?: boolean; separatorMargin?: number};
9
10
  export function isGroup<Option, Group extends GroupBase<Option>>(
10
11
  optionOrGroup: Option | Group
11
12
  ): optionOrGroup is Group {