@fibery/ui-kit 4.0.0 → 4.1.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 (54) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/esfint.config.mjs +0 -17
  3. package/package.json +5 -6
  4. package/scripts/generate-icons.mjs +45 -44
  5. package/src/actions-menu/actions-menu-item.tsx +1 -1
  6. package/src/actions-menu/context-actions-menu.tsx +1 -1
  7. package/src/animated-height-container.tsx +1 -1
  8. package/src/antd/ant-modal.tsx +21 -21
  9. package/src/antd/styles.ts +2 -0
  10. package/src/app-icon-with-fallback.tsx +3 -7
  11. package/src/app-icon-wrapper.tsx +1 -13
  12. package/src/breadcrumb.tsx +5 -1
  13. package/src/color-utils.test.ts +2 -2
  14. package/src/color-utils.ts +2 -2
  15. package/src/copy-to-clipboard.ts +14 -3
  16. package/src/date-picker/contexts.ts +6 -3
  17. package/src/date-picker/date-range-picker.tsx +1 -2
  18. package/src/date-picker/single-date-picker.tsx +1 -2
  19. package/src/day-select/iso-week-day-select.tsx +2 -2
  20. package/src/design-system/colors.ts +3 -3
  21. package/src/emoji-picker/icon-emoji-picker.tsx +3 -3
  22. package/src/icons/ast/index.tsx +446 -446
  23. package/src/icons/icon.tsx +14 -2
  24. package/src/icons/icons-integrity.test.ts +145 -0
  25. package/src/icons/react/index.tsx +446 -446
  26. package/src/images-gallery/images-gallery.tsx +2 -2
  27. package/src/images-gallery/slide-buttons.tsx +2 -4
  28. package/src/lists/actions-menu-row-surface.tsx +1 -1
  29. package/src/media-query-utils.ts +1 -1
  30. package/src/mobile-keyboard-aware-popup.tsx +1 -1
  31. package/src/number-input/decimal.ts +6 -9
  32. package/src/number-input/number-input-inline-with-autosize.tsx +1 -1
  33. package/src/online-users.tsx +4 -5
  34. package/src/palettes/inspect.defs.colors.neutral-arch.test.ts +4 -4
  35. package/src/palettes/inspect.defs.colors.neutral-user.test.ts +4 -4
  36. package/src/palettes/inspect.defs.colors.warm-arch.test.ts +4 -4
  37. package/src/palettes/inspect.defs.colors.warm-user.test.ts +4 -4
  38. package/src/popover/index.tsx +4 -4
  39. package/src/popover/mobile-popover-context.tsx +1 -1
  40. package/src/popover/modifiers.tsx +1 -1
  41. package/src/rich-input-loader.tsx +1 -1
  42. package/src/select/components/menu-list-virtua.tsx +15 -16
  43. package/src/select/components/menu-list-virtualized.tsx +26 -29
  44. package/src/select/components/menu.tsx +3 -3
  45. package/src/select/index.tsx +1 -1
  46. package/src/select/reflection.ts +4 -5
  47. package/src/select/select.tsx +3 -3
  48. package/src/select/styles.ts +1 -7
  49. package/src/select/util.ts +2 -2
  50. package/src/theming.generated.css +8 -8
  51. package/src/theming.generated.ts +8 -8
  52. package/src/type-badge.tsx +1 -2
  53. package/src/use-on-screen-keyboard-data.tsx +1 -1
  54. package/src/with-data.tsx +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @fibery/ui-kit
2
2
 
3
+ ## 4.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fb2ba1d: Add `isRecord`, `noop`, `identity`, `truthy` to `_.ts` in `@fibery/helpers`; drop `lodash` from `@fibery/ui-kit` dependencies.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [fb2ba1d]
12
+ - @fibery/helpers@3.1.0
13
+ - @fibery/react@1.5.4
14
+
3
15
  ## 4.0.0
4
16
 
5
17
  ### Major Changes
package/esfint.config.mjs CHANGED
@@ -13,21 +13,4 @@ export default [
13
13
  "unicorn/filename-case": "off",
14
14
  },
15
15
  },
16
- {
17
- files: ["src/design-system/**/*"],
18
- rules: {
19
- "no-restricted-imports": [
20
- "error",
21
- {
22
- patterns: [
23
- {group: ["@fibery/ui-kit/*"]},
24
- {
25
- group: ["lodash", "lodash/*", "lodash-es", "lodash-es/*"],
26
- message: "lodash is not allowed in design-system",
27
- },
28
- ],
29
- },
30
- ],
31
- },
32
- },
33
16
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fibery/ui-kit",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "dependencies": {
@@ -33,10 +33,10 @@
33
33
  "d3-shape": "1.3.7",
34
34
  "date-fns": "2.29.2",
35
35
  "invariant": "2.2.4",
36
- "lodash": "4.17.21",
37
36
  "lru-cache": "11.2.2",
38
37
  "md5": "2.2.1",
39
38
  "moment": "2.29.4",
39
+ "node-types-local": "npm:@types/node@^24",
40
40
  "photoswipe": "5.4.4",
41
41
  "popper-max-size-modifier": "0.2.0",
42
42
  "prop-types": "15.7.2",
@@ -60,8 +60,8 @@
60
60
  "tabbable": "5.2.1",
61
61
  "virtua": "0.48.8",
62
62
  "@fibery/emoji-data": "2.7.3",
63
- "@fibery/helpers": "3.0.0",
64
- "@fibery/react": "1.5.3"
63
+ "@fibery/helpers": "3.1.0",
64
+ "@fibery/react": "1.5.4"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@babel/core": "7.28.5",
@@ -74,7 +74,6 @@
74
74
  "@types/culori": "4.0.1",
75
75
  "@types/history": "4.7.11",
76
76
  "@types/invariant": "2.2.34",
77
- "@types/lodash": "4.14.172",
78
77
  "@types/prop-types": "15.7.5",
79
78
  "@types/react": "18.3.12",
80
79
  "@types/react-color": "2.13.5",
@@ -82,7 +81,7 @@
82
81
  "@typescript/native-preview": "beta",
83
82
  "csstype": "3.0.8",
84
83
  "fs-extra": "10.0.0",
85
- "glob": "7.1.7",
84
+ "glob": "13.0.6",
86
85
  "react": "18.3.1",
87
86
  "react-dom": "18.3.1",
88
87
  "svg-parser": "2.0.4",
@@ -1,15 +1,29 @@
1
1
  import {readFile} from "fs/promises";
2
- import path from "path";
3
- import {fileURLToPath} from "url";
4
- import {promisify} from "util";
2
+ import path from "node:path";
3
+ import {fileURLToPath} from "node:url";
5
4
 
6
5
  import fs from "fs-extra";
7
- import glob from "glob";
8
- import _ from "lodash";
6
+ import {glob} from "glob";
9
7
  import {parse} from "svg-parser";
10
8
  import svgo from "svgo";
11
9
  import {recursiveReduce} from "unist-util-reduce";
12
10
 
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+ const WORD_RE = /[A-Z]+(?![a-z])|[A-Z]?[a-z]+|\d+/g;
15
+ const words = (s) => String(s).match(WORD_RE) ?? [];
16
+ const cap = (w) => w[0].toUpperCase() + w.slice(1).toLowerCase();
17
+ const pascalCase = (s) => words(s).map(cap).join("");
18
+ const camelCase = (s) =>
19
+ words(s)
20
+ .map((w, i) => (i ? cap(w) : w.toLowerCase()))
21
+ .join("");
22
+ const kebabCase = (s) =>
23
+ words(s)
24
+ .map((w) => w.toLowerCase())
25
+ .join("-");
26
+
13
27
  const svgoConfig = (prefix) => ({
14
28
  plugins: [
15
29
  {
@@ -24,55 +38,55 @@ const svgoConfig = (prefix) => ({
24
38
  ],
25
39
  });
26
40
 
27
- const __filename = fileURLToPath(import.meta.url);
28
- const __dirname = path.dirname(__filename);
29
-
30
- const preludeTemplate = `/**
41
+ const preludeTemplate = ({source, generator}) => `/**
31
42
  * This file was automatically generated. Do not edit manually.
32
- * <% if (source) { %>
33
- * @see import("<%= source %>")<% } %>
34
- * @see import("<%= generator %>")
43
+ * ${
44
+ source
45
+ ? `
46
+ * @see import("${source}")`
47
+ : ""
48
+ }
49
+ * @see import("${generator}")
35
50
  */
36
51
  `;
37
52
 
38
- const render = _.template(
39
- `${preludeTemplate}
53
+ const render = ({source, generator, svgIdentifier, svgIdentifierPath}) =>
54
+ `${preludeTemplate({source, generator})}
40
55
  import {forwardRef} from "react";
41
- import <%= svgIdentifier %>Svg from "../ast/<%= svgIdentifierPath %>";
56
+ import ${svgIdentifier}Svg from "../ast/${svgIdentifierPath}";
42
57
  import {Icon} from "../icon";
43
58
  import type {IconBaseProps} from "../types";
44
59
 
45
- const <%= svgIdentifier %> = forwardRef<SVGSVGElement, IconBaseProps>(function <%= svgIdentifier %>(
60
+ const ${svgIdentifier} = forwardRef<SVGSVGElement, IconBaseProps>(function ${svgIdentifier}(
46
61
  props: IconBaseProps,
47
62
  ref: React.Ref<SVGSVGElement>
48
- ) {return <Icon {...props} className={props.className} ref={ref} icon={<%= svgIdentifier %>Svg} />});
63
+ ) {return <Icon {...props} className={props.className} ref={ref} icon={${svgIdentifier}Svg} />});
49
64
 
50
- export default <%= svgIdentifier %>;
51
- `.trim()
52
- );
65
+ export default ${svgIdentifier};
66
+ `.trim();
53
67
 
54
- const defTemplate = _.template(`${preludeTemplate}
68
+ const defTemplate = ({source, generator, identifier, content}) => `${preludeTemplate({source, generator})}
55
69
  import type {IconDefinition} from "../types";
56
70
 
57
- const <%= identifier %>: IconDefinition = <%= content %>;
71
+ const ${identifier}: IconDefinition = ${content};
58
72
 
59
- export default <%= identifier %>;
60
- `);
73
+ export default ${identifier};
74
+ `;
61
75
 
62
76
  const iconsDir = path.join(__dirname, "../src/icons");
63
77
  const iconsSvgPathDir = path.join(iconsDir, "./svg");
64
78
  const iconsReactPathDir = path.join(iconsDir, "./react");
65
79
  const iconsAstPathDir = path.join(iconsDir, "./ast");
66
80
  const pattern = "**/*.svg";
67
- const icons = await promisify(glob)(pattern, {cwd: iconsSvgPathDir});
81
+ const icons = await glob(pattern, {cwd: iconsSvgPathDir});
68
82
 
69
83
  await fs.remove(iconsReactPathDir);
70
84
  await fs.remove(iconsAstPathDir);
71
85
 
72
86
  const generator = path.relative(iconsReactPathDir, __filename);
73
- let barrelExportContent = _.template(preludeTemplate)({source: null, generator: generator}) + "\n";
87
+ let barrelExportContent = preludeTemplate({source: null, generator: generator}) + "\n";
74
88
  for (const icon of icons) {
75
- const name = _.upperFirst(_.camelCase(icon.replace("/", "-").replace(".svg", "")));
89
+ const name = pascalCase(icon.replace("/", "-").replace(".svg", ""));
76
90
  const resultFileName = icon.replaceAll("/", "-").replace(".svg", "");
77
91
  const iconPath = path.join(iconsSvgPathDir, icon);
78
92
  const source = path.relative(iconsReactPathDir, iconPath);
@@ -97,17 +111,17 @@ for (const icon of icons) {
97
111
  value.split(";").forEach((styleItem) => {
98
112
  const [styleKey, styleValue] = styleItem.split(":").map((s) => s.trim());
99
113
  if (styleKey && styleValue) {
100
- style[_.camelCase(styleKey)] = styleValue;
114
+ style[camelCase(styleKey)] = styleValue;
101
115
  }
102
116
  });
103
- return [_.camelCase(key), style];
117
+ return [camelCase(key), style];
104
118
  }
105
- return [_.camelCase(key), value];
119
+ return [camelCase(key), value];
106
120
  })
107
121
  ),
108
122
  };
109
123
  }),
110
- name: _.kebabCase(name),
124
+ name: kebabCase(name),
111
125
  }),
112
126
  })
113
127
  );
@@ -126,16 +140,3 @@ for (const icon of icons) {
126
140
  }
127
141
  await fs.outputFile(path.join(iconsReactPathDir, `./index.tsx`), barrelExportContent);
128
142
  await fs.outputFile(path.join(iconsAstPathDir, `./index.tsx`), barrelExportContent);
129
-
130
- // import {getSvg, iconNames} from "../Icon";
131
- //
132
- // it.skip("generate", async () => {
133
- // console.log(iconNames);
134
- // for (const iconName of iconNames) {
135
- // await fs.outputFile(
136
- // path.join(__dirname, "./icons", iconName.replace(":icon", "svg") + ".svg"),
137
- // `<?xml version="1.0" standalone="no"?>\n` + getSvg({name: iconName})
138
- // );
139
- // }
140
- // expect(true).toEqual(true);
141
- // });
@@ -1,7 +1,7 @@
1
+ import {noop} from "@fibery/helpers/src/_";
1
2
  import {stopPropagation} from "@fibery/react/src/stop-propagation";
2
3
  import {useCallbackRef} from "@fibery/react/src/use-callback-ref";
3
4
  import {css, cx} from "@linaria/core";
4
- import noop from "lodash/noop";
5
5
  import type {FunctionComponent, ReactNode} from "react";
6
6
  import {memo, useId} from "react";
7
7
 
@@ -1,6 +1,6 @@
1
+ import {noop} from "@fibery/helpers/src/_";
1
2
  import {preventDefault} from "@fibery/react/src/prevent-default";
2
3
  import {useControllableState} from "@fibery/react/src/use-controllable-state";
3
- import noop from "lodash/noop";
4
4
  import {useState} from "react";
5
5
 
6
6
  import * as ContextMenu from "../context-menu";
@@ -1,6 +1,6 @@
1
+ import {noop} from "@fibery/helpers/src/_";
1
2
  import {useComposedRefs} from "@fibery/react/src/use-composed-refs";
2
3
  import {useSize} from "@fibery/react/src/use-size";
3
- import noop from "lodash/noop";
4
4
  import type {ComponentProps} from "react";
5
5
  import React, {forwardRef, useEffect, useRef, useState} from "react";
6
6
 
@@ -10,7 +10,7 @@ import {ThemeProvider} from "../theme-provider";
10
10
  const defaultCloseIcon = <CloseIcon />;
11
11
 
12
12
  const modalCss = css`
13
- & .ant-modal-mask {
13
+ :where(&) .ant-modal-mask {
14
14
  position: fixed;
15
15
  inset: 0;
16
16
  z-index: 1000;
@@ -18,7 +18,7 @@ const modalCss = css`
18
18
  background-color: ${themeVars.modalBg};
19
19
  }
20
20
 
21
- & .ant-modal-wrap {
21
+ :where(&) .ant-modal-wrap {
22
22
  position: fixed;
23
23
  inset: 0;
24
24
  z-index: 1000;
@@ -27,7 +27,7 @@ const modalCss = css`
27
27
  -webkit-overflow-scrolling: touch;
28
28
  }
29
29
 
30
- & .ant-modal {
30
+ :where(&) .ant-modal {
31
31
  position: relative;
32
32
  top: 100px;
33
33
  margin: 0 auto;
@@ -35,11 +35,11 @@ const modalCss = css`
35
35
  pointer-events: none;
36
36
  }
37
37
 
38
- & .ant-modal-centered {
38
+ :where(&) .ant-modal-centered {
39
39
  text-align: center;
40
40
  }
41
41
 
42
- & .ant-modal-centered::before {
42
+ :where(&) .ant-modal-centered::before {
43
43
  display: inline-block;
44
44
  width: 0;
45
45
  height: 100%;
@@ -47,7 +47,7 @@ const modalCss = css`
47
47
  content: "";
48
48
  }
49
49
 
50
- & .ant-modal-centered .ant-modal {
50
+ :where(&) .ant-modal-centered .ant-modal {
51
51
  top: 0;
52
52
  display: inline-block;
53
53
  padding-bottom: 0;
@@ -55,7 +55,7 @@ const modalCss = css`
55
55
  vertical-align: middle;
56
56
  }
57
57
 
58
- & .ant-modal-content {
58
+ :where(&) .ant-modal-content {
59
59
  position: relative;
60
60
  box-shadow: ${themeVars.shadowPopup};
61
61
  margin-left: ${space.s6}px;
@@ -65,11 +65,11 @@ const modalCss = css`
65
65
  pointer-events: auto;
66
66
  }
67
67
 
68
- & .ant-modal-body {
68
+ :where(&) .ant-modal-body {
69
69
  padding: ${space.s20}px;
70
70
  }
71
71
 
72
- & .ant-modal-close {
72
+ :where(&) .ant-modal-close {
73
73
  position: absolute;
74
74
  top: 0;
75
75
  right: 0;
@@ -87,34 +87,34 @@ const modalCss = css`
87
87
  cursor: pointer;
88
88
  }
89
89
 
90
- & .ant-fade-enter,
91
- & .ant-fade-appear {
90
+ :where(&) .ant-fade-enter,
91
+ :where(&) .ant-fade-appear {
92
92
  opacity: 0;
93
93
  }
94
94
 
95
- & .ant-fade-enter-active,
96
- & .ant-fade-appear-active {
95
+ :where(&) .ant-fade-enter-active,
96
+ :where(&) .ant-fade-appear-active {
97
97
  opacity: 1;
98
98
  transition: opacity ${transitions.normal};
99
99
  }
100
100
 
101
- & .ant-fade-leave {
101
+ :where(&) .ant-fade-leave {
102
102
  opacity: 1;
103
103
  }
104
104
 
105
- & .ant-fade-leave-active {
105
+ :where(&) .ant-fade-leave-active {
106
106
  opacity: 0;
107
107
  transition: opacity ${transitions.normal};
108
108
  }
109
109
 
110
- & .ant-zoom-enter,
111
- & .ant-zoom-appear {
110
+ :where(&) .ant-zoom-enter,
111
+ :where(&) .ant-zoom-appear {
112
112
  opacity: 0;
113
113
  transform: scale(0.96);
114
114
  }
115
115
 
116
- & .ant-zoom-enter-active,
117
- & .ant-zoom-appear-active {
116
+ :where(&) .ant-zoom-enter-active,
117
+ :where(&) .ant-zoom-appear-active {
118
118
  opacity: 1;
119
119
  transform: scale(1);
120
120
  transition:
@@ -122,12 +122,12 @@ const modalCss = css`
122
122
  transform ${transitions.normal};
123
123
  }
124
124
 
125
- & .ant-zoom-leave {
125
+ :where(&) .ant-zoom-leave {
126
126
  opacity: 1;
127
127
  transform: scale(1);
128
128
  }
129
129
 
130
- & .ant-zoom-leave-active {
130
+ :where(&) .ant-zoom-leave-active {
131
131
  opacity: 0;
132
132
  transform: scale(0.96);
133
133
  transition:
@@ -182,6 +182,8 @@ export const inputStyles = css`
182
182
  },
183
183
  "&& input:hover:not(:focus):not(:disabled), && textarea:hover:not(:focus):not(:disabled), && .ant-input:hover:not(:focus):not(:disabled)":
184
184
  inputOverrides.hover,
185
+ "&& .ant-input-affix-wrapper > input.ant-input:focus, && .ant-input-affix-wrapper > input.ant-input:active":
186
+ inputOverrides.focus,
185
187
  "&& input:focus, && textarea:focus, && input:active, && textarea:active": inputOverrides.focus,
186
188
  "&& .ant-input-disabled, && .ant-input-disabled:hover, && .ant-input-disabled:focus, && .ant-input-disabled:active":
187
189
  inputOverrides.disabled,
@@ -1,11 +1,9 @@
1
1
  import {css} from "@linaria/core";
2
- import fromPairs from "lodash/fromPairs";
3
2
  import type {FC} from "react";
4
3
  import {lazy, Suspense} from "react";
5
4
 
6
5
  import {AppIconWrapper} from "./app-icon-wrapper";
7
6
  import {alpha, iconSize, layout} from "./design-system";
8
- import {isFlagPresent} from "./flags";
9
7
  import {Icon} from "./icons/icon";
10
8
  import {useTheme} from "./theme-provider";
11
9
  import {useIsPhone} from "./use-is-phone";
@@ -94,7 +92,7 @@ type UglyExternalIconProps = Omit<UglyExternalIconImplProps, "appIconsLookup">;
94
92
 
95
93
  const UglyExternalIcon = lazy(async () => {
96
94
  const appIcons = await import("./app-icons.json");
97
- const appIconsLookup = fromPairs(
95
+ const appIconsLookup = Object.fromEntries(
98
96
  appIcons.default.flatMap((appIcon) => appIcon.short_names.map((shortName) => [shortName, appIcon]))
99
97
  );
100
98
 
@@ -146,12 +144,10 @@ export const AppIcon: FC<AppIconProps> = ({icon, color: neutralColor, isSelected
146
144
  const isPhone = useIsPhone();
147
145
  const defaultIconSize = isPhone ? layout.mobileSidebarIconSize : layout.newMenuIconSize;
148
146
  const {iconSize = defaultIconSize, containerSize = defaultIconSize} = rest;
149
- const {fg, bg} = theme.fns.icon(neutralColor);
150
-
151
- const bgColor = !isFlagPresent("no-sidebar-app-icon-bg") ? alpha(fg, 0.2) : bg;
147
+ const {fg} = theme.fns.icon(neutralColor);
152
148
 
153
149
  return (
154
- <AppIconWrapper color={bgColor} borderless={isSelected} containerSize={containerSize}>
150
+ <AppIconWrapper color={alpha(fg, 0.2)} borderless={isSelected} containerSize={containerSize}>
155
151
  <AppIconWithFallback name={icon} color={fg} colorProcessed containerSize={containerSize} iconSize={iconSize} />
156
152
  </AppIconWrapper>
157
153
  );
@@ -3,20 +3,12 @@ import cn from "classnames";
3
3
  import type {ReactNode} from "react";
4
4
 
5
5
  import {border, layout} from "./design-system";
6
- import {isFlagPresent} from "./flags";
7
- import {useTheme} from "./theme-provider";
8
6
  import {useIsPhone} from "./use-is-phone";
9
7
 
10
8
  const appIconContainerStyle = css`
11
9
  border-radius: ${border.radius4}px;
12
10
  `;
13
11
 
14
- const appIconGradientStyle = css`
15
- ${{
16
- backgroundImage: "linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0))",
17
- }}
18
- `;
19
-
20
12
  export const AppIconWrapper = ({
21
13
  children,
22
14
  color,
@@ -28,16 +20,12 @@ export const AppIconWrapper = ({
28
20
  borderless?: boolean;
29
21
  containerSize?: number;
30
22
  }) => {
31
- const theme = useTheme();
32
23
  const isPhone = useIsPhone();
33
24
  const {containerSize = isPhone ? layout.mobileSidebarIconSize : layout.newMenuIconSize} = rest;
34
25
  return (
35
26
  <div
36
27
  style={{backgroundColor: borderless ? "transparent" : color, width: containerSize, height: containerSize}}
37
- className={cn(
38
- appIconContainerStyle,
39
- !borderless && isFlagPresent("no-sidebar-app-icon-bg") && theme.mode === "dark" && appIconGradientStyle
40
- )}
28
+ className={cn(appIconContainerStyle)}
41
29
  >
42
30
  {children}
43
31
  </div>
@@ -64,10 +64,14 @@ const liReset = css`
64
64
  padding: 0;
65
65
  `;
66
66
 
67
+ const separatorIconCss = css`
68
+ --fibery-icon-size: var(--breadcrumb-separator-size, 16px);
69
+ `;
70
+
67
71
  export const BreadcrumbSeparator = ({children, className, ...props}: React.ComponentProps<"li">) => {
68
72
  return (
69
73
  <li role="presentation" aria-hidden="true" className={cx(liReset, className)} {...props}>
70
- {children ?? <ArrowRight color={breadcrumbColor} containerSize={16} iconSize={16} />}
74
+ {children ?? <ArrowRight color={breadcrumbColor} className={separatorIconCss} />}
71
75
  </li>
72
76
  );
73
77
  };
@@ -1,5 +1,5 @@
1
1
  // oxlint-disable typescript/consistent-type-imports
2
- import {cssFromUnsafe, getColorDistance, getLuminance, isValidColor} from "./color-utils";
2
+ import {cssFromUnsafe, getColorDistance, getLuminance, isValidColor, invalidColorFallback} from "./color-utils";
3
3
 
4
4
  describe("color-utils", () => {
5
5
  describe("cssFromUnsafe", () => {
@@ -21,7 +21,7 @@ describe("color-utils", () => {
21
21
  });
22
22
 
23
23
  it("returns fallback for invalid color", () => {
24
- expect(cssFromUnsafe("invalid")).toMatchInlineSnapshot(`"rgb(73, 120, 212)"`);
24
+ expect(cssFromUnsafe("invalid")).toEqual(invalidColorFallback);
25
25
  });
26
26
  });
27
27
 
@@ -27,8 +27,8 @@ export const oklchFrom = converter("oklch");
27
27
  const mapToSrgb = toGamut("rgb", "oklch");
28
28
 
29
29
  export const isValidColor = (color: string) => parse(color) !== undefined;
30
-
31
- export function colorFromUnsafe(color: string, fallback = "hsl(220, 62%, 56%)"): Color {
30
+ export const invalidColorFallback = "rgb(94, 94, 94)";
31
+ export function colorFromUnsafe(color: string, fallback = invalidColorFallback): Color {
32
32
  const parsed = parse(resolveColorArg(color, "colorFromUnsafe") ?? "") ?? parse(fallback)!;
33
33
  if (!parsed) {
34
34
  throw new Error(`Failed to parse fallback color ${fallback}`);
@@ -1,8 +1,19 @@
1
- export const copyToClipboard = (text: string) => window.navigator.clipboard.writeText(text);
2
- export const copyUrlToClipboard = ({url, label}: {url: string; label?: string}) => {
1
+ function checkClipboard() {
2
+ if (!navigator.clipboard) {
3
+ throw new Error("Clipboard disabled by browser permissions policy");
4
+ }
5
+ }
6
+
7
+ export const copyToClipboard = async (text: string) => {
8
+ checkClipboard();
9
+ await navigator.clipboard.writeText(text);
10
+ };
11
+
12
+ export const copyUrlToClipboard = async ({url, label}: {url: string; label?: string}) => {
13
+ checkClipboard();
3
14
  const htmlString = `<a href="${url}" target="_blank">${label || url}</a>`;
4
15
 
5
- return window.navigator.clipboard.write([
16
+ await navigator.clipboard.write([
6
17
  new ClipboardItem({
7
18
  "text/html": new Blob([htmlString], {type: "text/html"}),
8
19
  "text/plain": url,
@@ -1,7 +1,10 @@
1
- import noop from "lodash/noop";
2
- import {createContext} from "react";
1
+ import {noop} from "@fibery/helpers/src/_";
2
+ import {createContext, type Dispatch, type SetStateAction} from "react";
3
3
 
4
- export const StartEndFocusContext = createContext({
4
+ export const StartEndFocusContext = createContext<{
5
+ focusOnStart: boolean;
6
+ setFocusOnStart: Dispatch<SetStateAction<boolean>>;
7
+ }>({
5
8
  focusOnStart: true,
6
9
  setFocusOnStart: noop,
7
10
  });
@@ -1,6 +1,5 @@
1
1
  import {stopPropagation} from "@fibery/react/src/stop-propagation";
2
2
  import {css} from "@linaria/core";
3
- import omit from "lodash/omit";
4
3
  import moment from "moment";
5
4
  import {forwardRef, useCallback, useRef} from "react";
6
5
  import {focusable} from "tabbable";
@@ -111,7 +110,7 @@ export const DateRangePicker = forwardRef<
111
110
  >
112
111
  <DateRangePickerPopupContent
113
112
  ref={ref}
114
- {...omit(props, ["onChange", "value"])}
113
+ {...props}
115
114
  onChange={onDatesChange}
116
115
  value={dateValue}
117
116
  onKeyDown={onDatePickerKeyDown}
@@ -1,6 +1,5 @@
1
1
  import {stopPropagation} from "@fibery/react/src/stop-propagation";
2
2
  import {css, cx} from "@linaria/core";
3
- import omit from "lodash/omit";
4
3
  import type {Moment} from "moment";
5
4
  import moment from "moment";
6
5
 
@@ -191,7 +190,7 @@ export const SingleDatePickerPopoverContent = forwardRef<
191
190
  {!allowRelativeDate || activeButton === "exact" ? (
192
191
  <SingleDatePickerPopupContent
193
192
  ref={ref}
194
- {...omit(props, ["onChange", "value"])}
193
+ {...props}
195
194
  onChange={onDateChange}
196
195
  value={!isRelativeValue && value ? (value as Moment).toDate() : null}
197
196
  onKeyDown={onDatePickerKeyDown}
@@ -1,10 +1,10 @@
1
- import range from "lodash/range";
2
1
  import moment from "moment";
3
2
 
4
3
  import type {Props} from "./day-select";
5
4
  import {DaySelect} from "./day-select";
6
5
 
7
- const isoWeekDays = range(1, 8).map((day) => {
6
+ const isoWeekDays = Array.from({length: 7}, (_, i) => {
7
+ const day = i + 1;
8
8
  return {
9
9
  key: String(day),
10
10
  title: moment().isoWeekday(day).format("dd").charAt(0),
@@ -560,9 +560,9 @@ export function defsFromPalette({
560
560
  colorBgListItemGeneral: transparentAlways,
561
561
  colorBgListItemGeneralHover: surface.level3_4,
562
562
  colorBgListItemGeneralFocus: surface.level4_6,
563
- colorBgListItemGeneralSelected: notificationSelected,
564
- colorBgListItemGeneralSelectedHover: notificationSelectedHover,
565
- colorBgListItemGeneralSelectedFocus: notificationSelectedHover,
563
+ colorBgListItemGeneralSelected: [accent[5], accentDark[5]],
564
+ colorBgListItemGeneralSelectedHover: [accent[6], accentDark[6]],
565
+ colorBgListItemGeneralSelectedFocus: [accent[6], accentDark[6]],
566
566
  colorBgListItemGeneralDisabled: transparentAlways,
567
567
  colorTextListItemGeneralDisabled: fg.muted,
568
568
  colorBgListItemGeneralSelectedDisabled: [accent[5], accentDark[5]],
@@ -1,5 +1,5 @@
1
+ import {truthy} from "@fibery/helpers/src/_";
1
2
  import {css} from "@linaria/core";
2
- import compact from "lodash/compact";
3
3
  import {useState} from "react";
4
4
 
5
5
  import {Button} from "../button/button";
@@ -48,7 +48,7 @@ export const IconEmojiPicker: React.FC<IconEmojiPickerProps> = ({
48
48
  color,
49
49
  getHoverColor,
50
50
  }) => {
51
- const items = compact([
51
+ const items = [
52
52
  onEmojiSelect && {
53
53
  key: "emojis" as const,
54
54
  label: "Emojis",
@@ -80,7 +80,7 @@ export const IconEmojiPicker: React.FC<IconEmojiPickerProps> = ({
80
80
  />
81
81
  ),
82
82
  },
83
- ]);
83
+ ].filter(truthy);
84
84
 
85
85
  const firstTab = items[0];
86
86
  const [activeTab, setActiveTab] = useState(defaultTab || firstTab?.key);