@fibery/ui-kit 3.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 (102) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/esfint.config.mjs +0 -17
  3. package/package.json +18 -13
  4. package/scripts/generate-icons.mjs +45 -44
  5. package/src/actions-menu/actions-menu-item.tsx +2 -1
  6. package/src/actions-menu/actions-menu.tsx +1 -3
  7. package/src/actions-menu/context-actions-menu.tsx +1 -1
  8. package/src/animated-height-container.tsx +1 -1
  9. package/src/antd/ant-modal.tsx +158 -9
  10. package/src/antd/ant-upload.tsx +285 -27
  11. package/src/antd/auto-complete.tsx +145 -0
  12. package/src/antd/auto-width-transparent-textarea.tsx +9 -2
  13. package/src/antd/global-overrides.ts +352 -0
  14. package/src/antd/index.tsx +3 -2
  15. package/src/antd/input-number.tsx +97 -11
  16. package/src/antd/input.tsx +127 -8
  17. package/src/antd/styles.ts +92 -32
  18. package/src/antd/tabs.tsx +139 -27
  19. package/src/app-icon-with-fallback.tsx +3 -7
  20. package/src/app-icon-wrapper.tsx +1 -13
  21. package/src/avatar.tsx +2 -56
  22. package/src/breadcrumb.tsx +5 -1
  23. package/src/color-adjuster.ts +24 -1
  24. package/src/color-utils.test.ts +2 -2
  25. package/src/color-utils.ts +2 -2
  26. package/src/copy-to-clipboard.ts +14 -3
  27. package/src/count-badge.tsx +10 -1
  28. package/src/date-picker/contexts.ts +6 -3
  29. package/src/date-picker/date-range-picker.tsx +7 -8
  30. package/src/date-picker/single-date-picker.tsx +3 -5
  31. package/src/date-picker/types.ts +1 -1
  32. package/src/date-picker/with-popup-control.tsx +3 -9
  33. package/src/day-select/iso-week-day-select.tsx +2 -2
  34. package/src/design-system/colors-callout.warm-dark.test.ts +22 -22
  35. package/src/design-system/colors-callout.warm-light.test.ts +19 -19
  36. package/src/design-system/colors-css.ts +1 -1
  37. package/src/design-system/colors-enum.dark.warm.test.ts +63 -63
  38. package/src/design-system/colors-enum.light.warm.test.ts +54 -54
  39. package/src/design-system/colors-highlight.warm-dark.test.ts +21 -21
  40. package/src/design-system/colors-highlight.warm-light.test.ts +21 -21
  41. package/src/design-system/colors.ts +4 -4
  42. package/src/design-system/fns.badge.dark.warm.test.ts +34 -34
  43. package/src/design-system/fns.badge.light.warm.test.ts +31 -31
  44. package/src/design-system/fns.deneutralize.test.ts +44 -0
  45. package/src/design-system/fns.icon.dark.warm.test.ts +21 -21
  46. package/src/design-system/fns.icon.light.warm.test.ts +17 -17
  47. package/src/design-system/fns.icon.ts +21 -13
  48. package/src/design-system/fns.ts +27 -3
  49. package/src/design-system/typography.ts +1 -1
  50. package/src/design-system.ts +1 -1
  51. package/src/emoji-picker/icon-emoji-picker.tsx +3 -3
  52. package/src/fibermoji-placeholder.tsx +2 -3
  53. package/src/icons/ast/index.tsx +446 -446
  54. package/src/icons/icon.tsx +23 -1
  55. package/src/icons/icons-integrity.test.ts +145 -0
  56. package/src/icons/react/index.tsx +446 -446
  57. package/src/icons/types.ts +1 -1
  58. package/src/images-gallery/images-gallery.tsx +2 -2
  59. package/src/images-gallery/slide-buttons.tsx +2 -4
  60. package/src/layout-styles.ts +5 -1
  61. package/src/link-input/components/ant-text-area-with-custom-read-state.tsx +2 -1
  62. package/src/lists/actions-menu-row-surface.tsx +1 -1
  63. package/src/media-query-utils.ts +5 -14
  64. package/src/mobile-keyboard-aware-popup.tsx +1 -1
  65. package/src/number-input/decimal.ts +6 -9
  66. package/src/number-input/number-input-inline-with-autosize.tsx +1 -1
  67. package/src/online-users.tsx +4 -5
  68. package/src/palettes/inspect.defs.colors.neutral-arch.test.ts +4 -4
  69. package/src/palettes/inspect.defs.colors.neutral-user.test.ts +4 -4
  70. package/src/palettes/inspect.defs.colors.warm-arch.test.ts +141 -141
  71. package/src/palettes/inspect.defs.colors.warm-user.test.ts +141 -141
  72. package/src/palettes/warm.ts +2 -0
  73. package/src/popover/index.tsx +4 -4
  74. package/src/popover/mobile-popover-context.tsx +1 -1
  75. package/src/popover/modifiers.tsx +1 -1
  76. package/src/rich-input-loader.tsx +1 -1
  77. package/src/root-theme-provider.test.tsx +1 -1
  78. package/src/select/components/menu-list-virtua.tsx +15 -16
  79. package/src/select/components/menu-list-virtualized.tsx +26 -29
  80. package/src/select/components/menu.tsx +3 -3
  81. package/src/select/index.tsx +4 -4
  82. package/src/select/reflection.ts +4 -5
  83. package/src/select/select-in-popover.tsx +34 -51
  84. package/src/select/select.tsx +5 -5
  85. package/src/select/styles.ts +1 -7
  86. package/src/select/util.ts +2 -2
  87. package/src/theme-provider.test.tsx +1 -1
  88. package/src/theme-provider.tsx +3 -9
  89. package/src/theme-snapshots.test.ts +9 -13
  90. package/src/{theming/build.ts → theming.build.ts} +6 -9
  91. package/src/{theming/theming-fibery.snapshot.css → theming.generated.css} +60 -60
  92. package/src/{theming/theming-fibery.snapshot.ts → theming.generated.ts} +60 -60
  93. package/src/type-badge.tsx +1 -2
  94. package/src/{themed-ink.tsx → type-label.tsx} +2 -5
  95. package/src/use-on-screen-keyboard-data.tsx +1 -1
  96. package/src/with-data.tsx +1 -1
  97. package/src/antd/auto-complete.d.ts +0 -2
  98. package/src/antd/auto-complete.ts +0 -37
  99. package/src/antd/tabs.d.ts +0 -5
  100. package/src/theming/index.ts +0 -20
  101. package/src/theming/theming-vzdbery.snapshot.css +0 -2001
  102. package/src/theming/theming-vzdbery.snapshot.ts +0 -2519
@@ -1,6 +1,6 @@
1
1
  import {css} from "@linaria/core";
2
2
 
3
- import {border, layout, shadows, space, textStyles, themeVars, transition} from "../design-system";
3
+ import {border, layout, shadows, space, textStyles, themeVars, transitions} from "../design-system";
4
4
 
5
5
  const inputVariables = {
6
6
  default: {
@@ -32,7 +32,7 @@ export const inputOverrides = {
32
32
  paddingRight: space.s12,
33
33
  border: 0,
34
34
  borderColor: themeVars.transparent,
35
- transition: `box-shadow ${transition}`,
35
+ transition: `box-shadow ${transitions.faster}`,
36
36
  boxShadow: `var(--input-border, ${themeVars.inputBorderColor})`,
37
37
  letterSpacing: 0,
38
38
  "&::placeholder": {
@@ -41,18 +41,18 @@ export const inputOverrides = {
41
41
  },
42
42
  hover: {
43
43
  boxShadow: `var(--input-hover-border, 0 0 0 1px ${themeVars.colorAccentStrokeHover}) !important`,
44
- transition: `box-shadow ${transition}`,
44
+ transition: `box-shadow ${transitions.faster}`,
45
45
  },
46
46
  focus: {
47
47
  boxShadow: `var(--input-focus-border, 0 0 0 1px ${themeVars.colorAccentStroke}), var(--input-focus-shadow) !important`,
48
- transition: `box-shadow ${transition}`,
48
+ transition: `box-shadow ${transitions.faster}`,
49
49
  },
50
50
  disabled: {
51
51
  color: `${themeVars.textColor}`,
52
52
  backgroundColor: `${themeVars.inputDisabledBgColor} !important`,
53
53
  borderColor: themeVars.transparent,
54
54
  boxShadow: `${themeVars.inputDisabledBorderColor} !important`,
55
- transition: `box-shadow ${transition}`,
55
+ transition: `box-shadow ${transitions.faster}`,
56
56
  WebkitTextFillColor: "currentColor",
57
57
  cursor: "default",
58
58
  "&:hover": {
@@ -69,6 +69,58 @@ export const inputStyles = css`
69
69
  }
70
70
  ${{
71
71
  ...textStyles.regular,
72
+ "& .ant-input, & .ant-input-affix-wrapper, & .ant-input-group-wrapper, & .ant-input-wrapper": {
73
+ boxSizing: "border-box",
74
+ },
75
+ "& .ant-input-affix-wrapper": {
76
+ display: "inline-flex",
77
+ alignItems: "center",
78
+ width: "100%",
79
+ position: "relative",
80
+ font: "inherit",
81
+ padding: 0,
82
+ border: "none",
83
+ boxShadow: "none",
84
+ backgroundColor: themeVars.transparent,
85
+ },
86
+ "& .ant-input-affix-wrapper > input.ant-input": {
87
+ minWidth: 0,
88
+ flex: 1,
89
+ padding: "0 12px",
90
+ border: "inherit",
91
+ outline: "inherit",
92
+ },
93
+ "& .ant-input-prefix, & .ant-input-suffix": {
94
+ display: "flex",
95
+ flex: "none",
96
+ alignItems: "center",
97
+ },
98
+ // antd source: spacing between adjacent icons when prefix/suffix holds multiple nodes.
99
+ "& .ant-input-prefix > *:not(:last-child), & .ant-input-suffix > *:not(:last-child)": {
100
+ marginRight: space.s8,
101
+ },
102
+ "& .ant-input-suffix": {
103
+ marginLeft: space.s6,
104
+ },
105
+ "& .ant-input-clear-icon": {
106
+ display: "inline-flex",
107
+ alignItems: "center",
108
+ color: themeVars.iconColor,
109
+ cursor: "pointer",
110
+ lineHeight: 1,
111
+ },
112
+ "& .ant-input-clear-icon-hidden": {
113
+ visibility: "hidden",
114
+ },
115
+ "& .ant-input-group-wrapper": {
116
+ display: "inline-block",
117
+ width: "100%",
118
+ },
119
+ "& .ant-input-wrapper.ant-input-group": {
120
+ display: "inline-flex",
121
+ width: "100%",
122
+ alignItems: "stretch",
123
+ },
72
124
  "& .ant-input-group-addon": {
73
125
  backgroundColor: themeVars.transparent,
74
126
  border: "none",
@@ -79,23 +131,37 @@ export const inputStyles = css`
79
131
  "&:focus": inputOverrides.focus,
80
132
  },
81
133
  "& .ant-input-group": {borderSpacing: 1, ...textStyles.regular},
82
- "& .ant-input": inputOverrides.main,
134
+ "& .ant-input": {
135
+ ...inputOverrides.main,
136
+ // antd source: .ant-input { width: 100%; min-width: 0 } — without it, bare <AntInput>
137
+ // doesn't fill its container and can't shrink inside a flex parent. AutoComplete's
138
+ // .ant-select-selection-search .ant-input rule (two-class selector) beats this on
139
+ // specificity for combobox inputs, so the nested-AntAutoComplete case is unaffected.
140
+ width: "100%",
141
+ minWidth: 0,
142
+ },
143
+ // antd source: long placeholders get ellipsis instead of wrap-clip.
144
+ "& .ant-input:placeholder-shown": {
145
+ textOverflow: "ellipsis",
146
+ },
147
+ // bordered={false} → wrap with `ant-input-borderless` class; strip the box-shadow ring + bg.
148
+ // antd source covers base + hover/focus/disabled with the same declarations.
149
+ // !important needed to beat inputOverrides.main's !important on backgroundColor/boxShadow.
150
+ "& .ant-input.ant-input-borderless, & .ant-input.ant-input-borderless:hover, & .ant-input.ant-input-borderless:focus, & .ant-input.ant-input-borderless:disabled, & .ant-input.ant-input-borderless[disabled]":
151
+ {
152
+ boxShadow: "none !important",
153
+ backgroundColor: `${themeVars.transparent} !important`,
154
+ },
155
+ // antd source: inner input inside affix-wrapper must not paint its own focus ring —
156
+ // the wrapper owns the visual focus state. Without this prefix/suffix usage shows a
157
+ // double ring.
158
+ "& .ant-input-affix-wrapper > .ant-input:focus": {
159
+ boxShadow: "none !important",
160
+ },
83
161
  "& .ant-input-status-error": inputVariables.error,
84
162
  "& .ant-input[readonly].ant-input[readonly]": {...inputOverrides.disabled, cursor: "inherit"},
85
163
  "& .ant-input:hover[readonly].ant-input:hover[readonly]": {...inputOverrides.disabled, cursor: "inherit"},
86
164
  "& .ant-input-affix-wrapper:hover .ant-input[readonly]": {...inputOverrides.disabled, cursor: "inherit"},
87
- "& .ant-input-affix-wrapper > input.ant-input": {
88
- padding: "0 12px",
89
- border: "inherit",
90
- outline: "inherit",
91
- },
92
- "& .ant-input-affix-wrapper": {
93
- font: "inherit",
94
- padding: 0,
95
- border: "none",
96
- boxShadow: "none",
97
- backgroundColor: themeVars.transparent,
98
- },
99
165
  "& .ant-input-affix-wrapper.ant-input-affix-wrapper-readonly": {
100
166
  border: "none !important",
101
167
  outline: "none !important",
@@ -104,32 +170,26 @@ export const inputStyles = css`
104
170
  "& .ant-input-affix-wrapper:hover .ant-input:not(.ant-input-disabled)": {
105
171
  borderColor: "transparent",
106
172
  },
107
- "& .ant-cascader-picker:focus .ant-cascader-input, & .ant-cascader-picker:focus:hover .ant-cascader-input":
108
- inputOverrides.focus,
109
- "& .ant-select-auto-complete.ant-select": {
110
- ...textStyles.regular,
111
- "& .ant-select-selection--single": {height: layout.inputHeight},
112
- "& .ant-input": inputOverrides.main,
113
- "& .ant-input.ant-input:hover": inputOverrides.hover,
114
- "& .ant-input.ant-input:focus": inputOverrides.focus,
115
- "& .ant-input.ant-input-disabled": inputOverrides.disabled,
116
- },
173
+ // antd source: textarea defaults — min-height anchors empty <AntTextArea> to a sane
174
+ // height, max-width prevents user-resize from blowing out the container, and
175
+ // vertical-align: bottom matches the baseline of a single-line <input> next to it.
117
176
  "& textarea.ant-input": {
118
177
  paddingTop: space.s8,
119
178
  paddingBottom: space.s8,
120
- },
121
- "& .ant-select-disabled .ant-select-selection": {
122
- backgroundColor: themeVars.transparent,
179
+ minHeight: layout.inputHeight,
180
+ maxWidth: "100%",
181
+ verticalAlign: "bottom",
123
182
  },
124
183
  "&& input:hover:not(:focus):not(:disabled), && textarea:hover:not(:focus):not(:disabled), && .ant-input:hover:not(:focus):not(:disabled)":
125
184
  inputOverrides.hover,
185
+ "&& .ant-input-affix-wrapper > input.ant-input:focus, && .ant-input-affix-wrapper > input.ant-input:active":
186
+ inputOverrides.focus,
126
187
  "&& input:focus, && textarea:focus, && input:active, && textarea:active": inputOverrides.focus,
127
188
  "&& .ant-input-disabled, && .ant-input-disabled:hover, && .ant-input-disabled:focus, && .ant-input-disabled:active":
128
189
  inputOverrides.disabled,
129
190
  "&& input::placeholder, && textarea::placeholder": {
130
191
  color: themeVars.inputPlaceholderTextColor,
131
192
  },
132
- "& .ant-input-affix-wrapper .ant-input-prefix": {left: space.s6},
133
193
  "& .ant-input-affix-wrapper .ant-input:not(:first-child)": {paddingLeft: space.s6 + space.s24 + space.s8}, // Match TitlePreview spacing
134
194
  }}
135
195
  `;
package/src/antd/tabs.tsx CHANGED
@@ -1,32 +1,108 @@
1
- import {css, cx} from "@linaria/core";
2
- import Tabs from "antd/lib/tabs";
1
+ import {css} from "@linaria/core";
2
+ import Tabs from "rc-tabs";
3
+ import type {ComponentProps} from "react";
3
4
  import {forwardRef} from "react";
4
- import "antd/lib/tabs/style/css";
5
5
 
6
- import {textStyles, space, themeVars} from "../design-system";
6
+ import {textStyles, space, themeVars, transitions} from "../design-system";
7
+ import {getWrapper} from "./get-wrapper";
7
8
  import {wrapWith} from "./utils";
8
9
 
10
+ type TabsPropsAny = ComponentProps<typeof Tabs>;
11
+
12
+ const PrefixedTabs = forwardRef<HTMLDivElement, TabsPropsAny>(function PrefixedTabs(props, ref) {
13
+ return <Tabs prefixCls="ant-tabs" {...props} ref={ref} />;
14
+ });
15
+
9
16
  export const tabsStyles = css`
10
17
  ${{
11
18
  width: "100%",
12
- "& .ant-tabs-bar": {
19
+ "& .ant-tabs": {
20
+ boxSizing: "border-box",
21
+ display: "flex",
22
+ overflow: "visible",
23
+ width: "100%",
24
+ },
25
+ "& .ant-tabs-top, & .ant-tabs-bottom": {
26
+ flexDirection: "column",
27
+ },
28
+ "& .ant-tabs-nav": {
29
+ display: "flex",
30
+ flex: "none",
31
+ alignItems: "center",
13
32
  margin: 0,
33
+ width: "100%",
34
+ position: "relative",
35
+ "&::before": {
36
+ position: "absolute",
37
+ right: 0,
38
+ bottom: 0,
39
+ left: 0,
40
+ borderBottom: themeVars.borderSolidFilterGroup,
41
+ content: "''",
42
+ },
14
43
  },
15
44
  "& .ant-tabs-nav-wrap": {
45
+ display: "flex",
46
+ flex: "auto",
47
+ overflow: "hidden",
48
+ whiteSpace: "nowrap",
49
+ position: "relative",
50
+ alignSelf: "stretch",
51
+ transform: "translate(0)",
16
52
  marginBottom: 1,
17
53
  marginLeft: space.s12,
18
54
  marginRight: space.s12,
19
55
  },
20
- "& .ant-tabs": {
21
- overflow: "visible",
56
+ "& .ant-tabs-nav-wrap::before, & .ant-tabs-nav-wrap::after": {
57
+ position: "absolute",
58
+ zIndex: 1,
59
+ opacity: 0,
60
+ transition: `opacity ${transitions.slow}`,
61
+ content: "''",
62
+ pointerEvents: "none",
63
+ },
64
+ "& .ant-tabs-nav-list": {
65
+ position: "relative",
66
+ display: "flex",
67
+ transition: `transform ${transitions.normal}`,
68
+ },
69
+ "& .ant-tabs-nav-operations": {
70
+ display: "flex",
71
+ alignSelf: "stretch",
72
+ },
73
+ "& .ant-tabs-nav-operations-hidden": {
74
+ position: "absolute",
75
+ visibility: "hidden",
76
+ pointerEvents: "none",
77
+ },
78
+ "& .ant-tabs-nav-more": {
79
+ position: "relative",
80
+ padding: `${space.s8}px ${space.s16}px`,
81
+ backgroundColor: themeVars.transparent,
82
+ border: 0,
83
+ },
84
+ "& .ant-tabs-nav-more::after": {
85
+ position: "absolute",
86
+ right: 0,
87
+ bottom: 0,
88
+ left: 0,
89
+ height: 5,
90
+ transform: "translateY(100%)",
91
+ content: "''",
92
+ },
93
+ "& .ant-tabs-extra-content": {
94
+ flex: "none",
95
+ },
96
+ "& .ant-tabs-centered .ant-tabs-nav-wrap:not([class*='ant-tabs-nav-wrap-ping'])": {
97
+ justifyContent: "center",
22
98
  },
23
99
  "& .ant-tabs-ink-bar": {
100
+ position: "absolute",
101
+ bottom: 0,
24
102
  backgroundColor: themeVars.colorAccent,
25
103
  height: 2,
26
- },
27
- "& .ant-tabs-nav": {
28
- margin: 0,
29
- width: "100%",
104
+ pointerEvents: "none",
105
+ transition: `left ${transitions.faster}, right ${transitions.faster}, width ${transitions.faster}`,
30
106
  },
31
107
  "& .ant-tabs-tab, & .ant-tabs-tab:hover, & .ant-tabs-tab-btn:active, & .ant-tabs-tabpane, & .ant-tabs-tab-active .ant-tabs-tab-btn":
32
108
  {
@@ -34,29 +110,65 @@ export const tabsStyles = css`
34
110
  textShadow: "none",
35
111
  },
36
112
  "& .ant-tabs-tab": {
113
+ position: "relative",
114
+ display: "inline-flex",
115
+ alignItems: "center",
37
116
  padding: "12px 12px",
38
117
  margin: `0 ${space.s8}px 0 0`,
118
+ cursor: "pointer",
119
+ outline: "none",
39
120
  },
40
121
  "& .ant-tabs-tab + .ant-tabs-tab": {
41
122
  margin: `0 ${space.s8}px 0 0`,
42
123
  },
43
- ".ant-tabs-top > .ant-tabs-nav::before": {
44
- borderBottom: themeVars.borderSolidFilterGroup,
124
+ "& .ant-tabs-tab-btn": {
125
+ outline: "none",
126
+ transition: `all ${transitions.slow}`,
127
+ },
128
+ "& .ant-tabs-tab-active .ant-tabs-tab-btn": {
129
+ color: themeVars.colorAccent,
130
+ },
131
+ "& .ant-tabs-content-holder": {
132
+ flex: "auto",
133
+ minWidth: 0,
134
+ minHeight: 0,
135
+ },
136
+ "& .ant-tabs-content": {
137
+ position: "relative",
138
+ display: "flex",
139
+ width: "100%",
140
+ },
141
+ "& .ant-tabs-tabpane": {
142
+ flex: "none",
143
+ width: "100%",
144
+ outline: "none",
145
+ },
146
+ "& .ant-tabs-tabpane-hidden": {
147
+ display: "none",
148
+ },
149
+ "& .ant-tabs-switch-appear, & .ant-tabs-switch-enter": {
150
+ transition: "none",
151
+ },
152
+ "& .ant-tabs-switch-appear-start, & .ant-tabs-switch-enter-start": {
153
+ opacity: 0,
154
+ },
155
+ "& .ant-tabs-switch-appear-active, & .ant-tabs-switch-enter-active": {
156
+ opacity: 1,
157
+ transition: `opacity ${transitions.slow}`,
158
+ },
159
+ "& .ant-tabs-switch-leave": {
160
+ position: "absolute",
161
+ inset: 0,
162
+ transition: "none",
163
+ },
164
+ "& .ant-tabs-switch-leave-start": {
165
+ opacity: 1,
166
+ },
167
+ "& .ant-tabs-switch-leave-active": {
168
+ opacity: 0,
169
+ transition: `opacity ${transitions.slow}`,
45
170
  },
46
171
  }}
47
172
  `;
48
173
 
49
- function getWrapper() {
50
- return forwardRef<HTMLDivElement, {children: React.ReactNode; className?: string}>(function TabWrapper(
51
- {children, className},
52
- ref
53
- ) {
54
- return (
55
- <div className={cx(className, tabsStyles)} ref={ref}>
56
- {children}
57
- </div>
58
- );
59
- });
60
- }
61
-
62
- export const AntTabs = wrapWith(getWrapper(), Tabs);
174
+ export const AntTabs = wrapWith(getWrapper(tabsStyles), PrefixedTabs);
@@ -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>
package/src/avatar.tsx CHANGED
@@ -3,12 +3,9 @@ import {css, cx} from "@linaria/core";
3
3
  import type {CSSProperties, FC} from "react";
4
4
  import {memo, useCallback, useMemo, useState} from "react";
5
5
 
6
- import {abbreviateName} from "./abbreviate-name";
7
- import {border, getDarkenColor, getObjectColorMemoized, space, textClasses, themeVars} from "./design-system";
8
- import {useFeatures} from "./features";
9
- import {FibermojiPlaceholder} from "./fibermoji-placeholder";
6
+ import {border, space, themeVars} from "./design-system";
7
+ import {FibermojiPlaceholder as AvatarPlaceholder} from "./fibermoji-placeholder";
10
8
  import {getShiftStyle} from "./icons/get-shift-style";
11
- import {useTheme} from "./theme-provider";
12
9
 
13
10
  export const getContainerStyle = memoize((containerSize: number) => {
14
11
  return {
@@ -167,57 +164,6 @@ const AvatarImage: FC<AvatarImageProps> = memo(function AvatarImage({
167
164
  );
168
165
  });
169
166
 
170
- const avatarPlaceholderStaticClassName = css`
171
- text-align: center;
172
- text-transform: uppercase;
173
- letter-spacing: -0.2px;
174
- `;
175
-
176
- const getAvatarPlaceholderStyle = ({
177
- size,
178
- backgroundColor,
179
- radius,
180
- }: {
181
- size: number;
182
- backgroundColor: string;
183
- radius?: number;
184
- }) => ({
185
- backgroundColor: getDarkenColor(backgroundColor),
186
- color: themeVars.whiteColor,
187
- lineHeight: `${size}px`,
188
- fontSize: size / 2.222222,
189
- width: size,
190
- height: size,
191
- borderRadius: radius ?? "50%",
192
- });
193
-
194
- type AvatarPlaceholderProps = {
195
- size: number;
196
- title?: string;
197
- radius?: number;
198
- hasAccess?: boolean;
199
- backgroundColor?: string;
200
- };
201
-
202
- const AvatarPlaceholder: FC<AvatarPlaceholderProps> = memo(function AvatarPlaceholder(props) {
203
- const features = useFeatures<{enableFibermojiAvatars: boolean}>();
204
- const theme = useTheme();
205
- if (features.enableFibermojiAvatars) {
206
- return <FibermojiPlaceholder {...props} />;
207
- }
208
-
209
- const {size, title, radius, backgroundColor = getObjectColorMemoized(title)} = props;
210
- const displayBg = theme.fns.deneutralize(backgroundColor);
211
- return (
212
- <div
213
- className={cx(textClasses.regular, avatarPlaceholderStaticClassName)}
214
- style={getAvatarPlaceholderStyle({size, backgroundColor: displayBg, radius})}
215
- >
216
- {abbreviateName(title)}
217
- </div>
218
- );
219
- });
220
-
221
167
  const getNullUserAvatarContainerStyle = ({containerSize}: {containerSize: number}) => ({
222
168
  width: containerSize,
223
169
  height: containerSize,
@@ -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
  };
@@ -7,6 +7,11 @@ export type AdjustSpec = {
7
7
  achromaticTint?: {h: number; c: number};
8
8
  strengths?: {common?: number; l?: number; c?: number; h?: number};
9
9
  lFade?: {lo?: number; hi?: number};
10
+ // Linear strength ramp from `min` at c=0 to 1 at c=hi, then 1 above.
11
+ // Softens pastels (Notion imports, vizydrop "Fibery" preset) without touching saturated swatches.
12
+ cFade?: {hi: number; min: number};
13
+ // Inputs with c ≤ greyC skip cFade so designer cool-greys keep full warmth.
14
+ greyC?: number;
10
15
  };
11
16
 
12
17
  const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
@@ -60,6 +65,21 @@ function makeStrengthFromL(lo: number, hi: number) {
60
65
  };
61
66
  }
62
67
 
68
+ function makeStrengthFromC(spec: AdjustSpec["cFade"]) {
69
+ if (!spec || spec.min >= 1 || spec.hi <= 0) {
70
+ return function strengthFromC(_c: number) {
71
+ return 1;
72
+ };
73
+ }
74
+ const {hi, min} = spec;
75
+ return function strengthFromC(c: number) {
76
+ if (c >= hi) {
77
+ return 1;
78
+ }
79
+ return lerp(min, 1, c / hi);
80
+ };
81
+ }
82
+
63
83
  export function makeAdjust(adjuster: AdjustSpec) {
64
84
  const derive = makeDerive(adjuster);
65
85
  const sCommon = adjuster.strengths?.common ?? 1;
@@ -70,14 +90,17 @@ export function makeAdjust(adjuster: AdjustSpec) {
70
90
  adjuster.lFade?.lo ?? WARM_L_LO_DEFAULT,
71
91
  adjuster.lFade?.hi ?? WARM_L_HI_DEFAULT
72
92
  );
93
+ const strengthFromC = makeStrengthFromC(adjuster.cFade);
94
+ const greyC = adjuster.greyC ?? 0;
73
95
  return function adjust(color: Oklch): Oklch {
74
96
  const hue = color.h ?? 0;
97
+ const isGrey = color.h === null || color.c <= greyC;
75
98
 
76
99
  const mulL = 1 + (derive(hue, (p) => p.mul.l) - 1) * sL;
77
100
  const powC = 1 + (derive(hue, (p) => p.pow.c) - 1) * sC;
78
101
  const addH = derive(hue, (p) => p.add.h) * sH;
79
102
 
80
- const strength = strengthFromL(color.l);
103
+ const strength = strengthFromL(color.l) * (isGrey ? 1 : strengthFromC(color.c));
81
104
 
82
105
  const l = Math.min(lerp(color.l, color.l * mulL, strength), 0.999);
83
106
  let c: number;
@@ -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,
@@ -34,8 +34,17 @@ const warningStyle = css`
34
34
  color: ${themeVars.textColor};
35
35
  `;
36
36
 
37
+ const countBadgeLimitOverride = Number.parseInt(
38
+ new URLSearchParams(window.location.search).get("count-badge-limit") ?? ""
39
+ );
40
+ const COUNT_BADGE_LIMIT = Number.isInteger(countBadgeLimitOverride) ? countBadgeLimitOverride : 99;
41
+
37
42
  export function CountBadge({className, count}: Props) {
38
- return <span className={cx(countStyle, countBadgeStyle, className)}>{count <= 99 ? count : "99+"}</span>;
43
+ return (
44
+ <span className={cx(countStyle, countBadgeStyle, className)}>
45
+ {count <= COUNT_BADGE_LIMIT ? count : `${COUNT_BADGE_LIMIT}+`}
46
+ </span>
47
+ );
39
48
  }
40
49
 
41
50
  export function CountBadgeWithNoLimits({count, warning}: {count: number | string; warning?: boolean}) {