@fibery/ui-kit 3.0.0 → 4.0.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 (64) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +14 -8
  3. package/src/actions-menu/actions-menu-item.tsx +1 -0
  4. package/src/actions-menu/actions-menu.tsx +1 -3
  5. package/src/antd/ant-modal.tsx +156 -7
  6. package/src/antd/ant-upload.tsx +285 -27
  7. package/src/antd/auto-complete.tsx +145 -0
  8. package/src/antd/auto-width-transparent-textarea.tsx +9 -2
  9. package/src/antd/global-overrides.ts +352 -0
  10. package/src/antd/index.tsx +3 -2
  11. package/src/antd/input-number.tsx +97 -11
  12. package/src/antd/input.tsx +127 -8
  13. package/src/antd/styles.ts +90 -32
  14. package/src/antd/tabs.tsx +139 -27
  15. package/src/avatar.tsx +2 -56
  16. package/src/color-adjuster.ts +24 -1
  17. package/src/count-badge.tsx +10 -1
  18. package/src/date-picker/date-range-picker.tsx +6 -6
  19. package/src/date-picker/single-date-picker.tsx +2 -3
  20. package/src/date-picker/types.ts +1 -1
  21. package/src/date-picker/with-popup-control.tsx +3 -9
  22. package/src/design-system/colors-callout.warm-dark.test.ts +22 -22
  23. package/src/design-system/colors-callout.warm-light.test.ts +19 -19
  24. package/src/design-system/colors-css.ts +1 -1
  25. package/src/design-system/colors-enum.dark.warm.test.ts +63 -63
  26. package/src/design-system/colors-enum.light.warm.test.ts +54 -54
  27. package/src/design-system/colors-highlight.warm-dark.test.ts +21 -21
  28. package/src/design-system/colors-highlight.warm-light.test.ts +21 -21
  29. package/src/design-system/colors.ts +1 -1
  30. package/src/design-system/fns.badge.dark.warm.test.ts +34 -34
  31. package/src/design-system/fns.badge.light.warm.test.ts +31 -31
  32. package/src/design-system/fns.deneutralize.test.ts +44 -0
  33. package/src/design-system/fns.icon.dark.warm.test.ts +21 -21
  34. package/src/design-system/fns.icon.light.warm.test.ts +17 -17
  35. package/src/design-system/fns.icon.ts +21 -13
  36. package/src/design-system/fns.ts +27 -3
  37. package/src/design-system/typography.ts +1 -1
  38. package/src/design-system.ts +1 -1
  39. package/src/fibermoji-placeholder.tsx +2 -3
  40. package/src/icons/icon.tsx +11 -1
  41. package/src/icons/types.ts +1 -1
  42. package/src/layout-styles.ts +5 -1
  43. package/src/link-input/components/ant-text-area-with-custom-read-state.tsx +2 -1
  44. package/src/media-query-utils.ts +4 -13
  45. package/src/palettes/inspect.defs.colors.warm-arch.test.ts +137 -137
  46. package/src/palettes/inspect.defs.colors.warm-user.test.ts +137 -137
  47. package/src/palettes/warm.ts +2 -0
  48. package/src/root-theme-provider.test.tsx +1 -1
  49. package/src/select/index.tsx +3 -3
  50. package/src/select/select-in-popover.tsx +34 -51
  51. package/src/select/select.tsx +2 -2
  52. package/src/theme-provider.test.tsx +1 -1
  53. package/src/theme-provider.tsx +3 -9
  54. package/src/theme-snapshots.test.ts +9 -13
  55. package/src/{theming/build.ts → theming.build.ts} +6 -9
  56. package/src/{theming/theming-fibery.snapshot.css → theming.generated.css} +52 -52
  57. package/src/{theming/theming-fibery.snapshot.ts → theming.generated.ts} +52 -52
  58. package/src/{themed-ink.tsx → type-label.tsx} +2 -5
  59. package/src/antd/auto-complete.d.ts +0 -2
  60. package/src/antd/auto-complete.ts +0 -37
  61. package/src/antd/tabs.d.ts +0 -5
  62. package/src/theming/index.ts +0 -20
  63. package/src/theming/theming-vzdbery.snapshot.css +0 -2001
  64. package/src/theming/theming-vzdbery.snapshot.ts +0 -2519
@@ -1,14 +1,91 @@
1
- import {css} from "@linaria/core";
2
- import Input from "antd/lib/input";
3
- import "antd/lib/input/style/css";
1
+ import {css, cx} from "@linaria/core";
2
+ import RcInput from "rc-input";
3
+ import type {InputProps, InputRef} from "rc-input";
4
+ import RcTextArea from "rc-textarea";
5
+ import type {TextAreaProps} from "rc-textarea";
6
+ import type {ResizableTextAreaRef} from "rc-textarea/lib/ResizableTextArea";
7
+ import {forwardRef, useImperativeHandle, useMemo, useRef} from "react";
4
8
 
5
- import {tabularNumsOff, textStyles, themeVars} from "../design-system";
9
+ import {space, tabularNumsOff, textStyles, themeVars} from "../design-system";
10
+ import CloseCircleFilledIcon from "../icons/react/cross-circle";
6
11
  import {mobileRootSelector} from "../mobile-styles";
7
12
  import {getWrapper} from "./get-wrapper";
8
13
  import {inputStyles} from "./styles";
9
14
  import {wrapWith} from "./utils";
10
15
 
11
- const TextArea = Input.TextArea;
16
+ const clearIconNode = <CloseCircleFilledIcon />;
17
+
18
+ export type {InputRef};
19
+
20
+ export interface InputFocusOptions extends FocusOptions {
21
+ cursor?: "start" | "end" | "all";
22
+ }
23
+
24
+ export interface TextAreaRef {
25
+ focus: (options?: InputFocusOptions) => void;
26
+ blur: () => void;
27
+ resizableTextArea?: ResizableTextAreaRef;
28
+ }
29
+
30
+ type AntInputProps = Omit<InputProps, "status"> & {
31
+ status?: "error";
32
+ bordered?: boolean;
33
+ };
34
+
35
+ const Input = forwardRef<InputRef, AntInputProps>(function Input(
36
+ {status, className, allowClear, bordered = true, ...props},
37
+ ref
38
+ ) {
39
+ const statusClass = status === "error" ? "ant-input-status-error" : undefined;
40
+ const allowClearProp = useMemo(() => {
41
+ if (allowClear === true) {
42
+ return {clearIcon: clearIconNode};
43
+ }
44
+ return allowClear;
45
+ }, [allowClear]);
46
+ return (
47
+ <RcInput
48
+ prefixCls="ant-input"
49
+ ref={ref}
50
+ allowClear={allowClearProp}
51
+ className={cx(className, statusClass, !bordered && "ant-input-borderless")}
52
+ {...props}
53
+ />
54
+ );
55
+ });
56
+
57
+ type AntTextAreaProps = Omit<TextAreaProps, "status"> & {
58
+ status?: "error";
59
+ };
60
+
61
+ const TextArea = forwardRef<TextAreaRef, AntTextAreaProps>(function TextArea({status, className, ...props}, ref) {
62
+ const innerRef = useRef<RcTextArea>(null);
63
+ useImperativeHandle(ref, () => ({
64
+ focus: (options) => {
65
+ const textarea = innerRef.current?.resizableTextArea?.textArea;
66
+ if (!textarea) {
67
+ return;
68
+ }
69
+ textarea.focus(options);
70
+ const len = textarea.value.length;
71
+ if (options?.cursor === "start") {
72
+ textarea.setSelectionRange(0, 0);
73
+ } else if (options?.cursor === "end") {
74
+ textarea.setSelectionRange(len, len);
75
+ } else if (options?.cursor === "all") {
76
+ textarea.setSelectionRange(0, len);
77
+ }
78
+ },
79
+ blur: () => innerRef.current?.blur(),
80
+ get resizableTextArea() {
81
+ return innerRef.current?.resizableTextArea;
82
+ },
83
+ }));
84
+ const statusClass = status === "error" ? "ant-input-status-error" : undefined;
85
+ return (
86
+ <RcTextArea prefixCls="ant-input" ref={innerRef} className={cx("ant-input", className, statusClass)} {...props} />
87
+ );
88
+ });
12
89
 
13
90
  export const transparentInputStyle = css`
14
91
  ${{
@@ -18,6 +95,7 @@ export const transparentInputStyle = css`
18
95
  "& input, & textarea, & .ant-input-affix-wrapper": {
19
96
  padding: 0,
20
97
  border: "none",
98
+ outline: "none",
21
99
  width: "100%",
22
100
  resize: "none",
23
101
  borderRadius: 0,
@@ -29,6 +107,50 @@ export const transparentInputStyle = css`
29
107
  ...textStyles.big,
30
108
  },
31
109
  },
110
+ "& .ant-input-affix-wrapper": {
111
+ display: "inline-flex",
112
+ alignItems: "center",
113
+ position: "relative",
114
+ minWidth: 0,
115
+ },
116
+ "& .ant-input-affix-wrapper > .ant-input": {
117
+ flex: 1,
118
+ minWidth: 0,
119
+ fontSize: "inherit",
120
+ border: "none",
121
+ outline: "none",
122
+ },
123
+ "& .ant-input-affix-wrapper > .ant-input:focus": {
124
+ boxShadow: "none !important",
125
+ },
126
+ "& .ant-input-affix-wrapper::before": {
127
+ width: 0,
128
+ visibility: "hidden",
129
+ content: "'\\a0'",
130
+ },
131
+ "& .ant-input-prefix, & .ant-input-suffix": {
132
+ display: "flex",
133
+ flex: "none",
134
+ alignItems: "center",
135
+ },
136
+ "& .ant-input-prefix > *:not(:last-child), & .ant-input-suffix > *:not(:last-child)": {
137
+ marginRight: space.s8,
138
+ },
139
+ "& .ant-input-prefix": {
140
+ marginRight: space.s4,
141
+ },
142
+ "& .ant-input-suffix": {
143
+ marginLeft: space.s4,
144
+ },
145
+ "& .ant-input-clear-icon": {
146
+ display: "inline-flex",
147
+ alignItems: "center",
148
+ lineHeight: 1,
149
+ cursor: "pointer",
150
+ },
151
+ "& .ant-input-clear-icon-hidden": {
152
+ visibility: "hidden",
153
+ },
32
154
  "& textarea.ant-input": {
33
155
  fontVariant: "inherit",
34
156
  },
@@ -54,8 +176,5 @@ export const StyledTextArea = wrapWith(InputWrapper, TextArea);
54
176
 
55
177
  export const TransparentInputWrapper = getWrapper(transparentInputStyle);
56
178
 
57
- export type TextAreaRef = React.ElementRef<typeof TextArea>;
58
179
  export const AntTransparentTextArea = wrapWith(TransparentInputWrapper, TextArea);
59
-
60
- export type InputRef = React.ElementRef<typeof Input>;
61
180
  export const AntTransparentInput = wrapWith(TransparentInputWrapper, Input);
@@ -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,22 +170,15 @@ 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,
@@ -129,7 +188,6 @@ export const inputStyles = css`
129
188
  "&& input::placeholder, && textarea::placeholder": {
130
189
  color: themeVars.inputPlaceholderTextColor,
131
190
  },
132
- "& .ant-input-affix-wrapper .ant-input-prefix": {left: space.s6},
133
191
  "& .ant-input-affix-wrapper .ant-input:not(:first-child)": {paddingLeft: space.s6 + space.s24 + space.s8}, // Match TitlePreview spacing
134
192
  }}
135
193
  `;
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);
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,
@@ -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;
@@ -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}) {