@fibery/ui-kit 1.40.3 → 1.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/eslint.config.mjs +16 -0
  3. package/package.json +11 -13
  4. package/src/a11y-color.test.ts +162 -0
  5. package/src/a11y-color.ts +13 -20
  6. package/src/actions-menu/actions-menu-item.tsx +9 -12
  7. package/src/actions-menu/context-actions-menu.tsx +8 -3
  8. package/src/ai/model.tsx +0 -1
  9. package/src/ai/temperature.tsx +0 -1
  10. package/src/animated-height-container.tsx +3 -3
  11. package/src/antd/ant-modal.tsx +10 -5
  12. package/src/antd/index.tsx +3 -0
  13. package/src/antd/input.tsx +0 -1
  14. package/src/antd/styles.ts +15 -15
  15. package/src/app-icon-with-fallback.tsx +6 -6
  16. package/src/app-icon-wrapper.tsx +2 -3
  17. package/src/app-icon.tsx +4 -4
  18. package/src/avatar.tsx +3 -3
  19. package/src/button/base-button.tsx +0 -1
  20. package/src/button/button.tsx +1 -0
  21. package/src/button/make-button-colors.ts +5 -13
  22. package/src/checkbox.tsx +2 -2
  23. package/src/collapsible-section.tsx +11 -1
  24. package/src/color-picker/ColorPickerOrLoader.js +2 -2
  25. package/src/color-utils.test.ts +307 -0
  26. package/src/color-utils.ts +215 -0
  27. package/src/command-menu/index.tsx +1 -0
  28. package/src/comment.tsx +3 -2
  29. package/src/context-menu/index.tsx +12 -7
  30. package/src/date-picker/contexts.ts +2 -2
  31. package/src/date-picker/date-range-picker.tsx +3 -4
  32. package/src/date-picker/single-date-picker.tsx +147 -119
  33. package/src/date-picker/types.ts +4 -3
  34. package/src/day-select/iso-week-day-select.tsx +2 -2
  35. package/src/day-select/week-day-select.tsx +2 -2
  36. package/src/delayed.tsx +0 -1
  37. package/src/design-system/alpha.test.ts +59 -0
  38. package/src/design-system/alpha.ts +41 -0
  39. package/src/design-system/animation.ts +10 -0
  40. package/src/design-system/colors-css.test.ts +39 -0
  41. package/src/design-system/colors-css.ts +25 -0
  42. package/src/design-system/colors-js.test.ts +232 -0
  43. package/src/design-system/colors-js.ts +107 -0
  44. package/src/design-system/colors.test.ts +74 -0
  45. package/src/design-system/colors.ts +759 -0
  46. package/src/design-system/date.ts +3 -0
  47. package/src/design-system/layout.ts +106 -0
  48. package/src/design-system/theme.test.ts +94 -0
  49. package/src/design-system/theme.ts +93 -0
  50. package/src/design-system/typography.ts +179 -0
  51. package/src/design-system/vars.test.ts +1679 -0
  52. package/src/design-system/vars.ts +52 -0
  53. package/src/design-system.test.ts +45 -22
  54. package/src/design-system.ts +25 -1339
  55. package/src/dropdown-menu/index.tsx +21 -16
  56. package/src/emoji-picker/app-icon-picker.tsx +5 -5
  57. package/src/emoji-picker/emoji-picker-content-with-color.tsx +4 -4
  58. package/src/emoji-picker/icon-emoji-picker.tsx +3 -3
  59. package/src/favorites-icon.tsx +1 -1
  60. package/src/fibermoji-placeholder.tsx +3 -3
  61. package/src/field-container.tsx +11 -2
  62. package/src/file-item/file-icon.tsx +169 -0
  63. package/src/file-item/file-menu-items.tsx +68 -0
  64. package/src/file-item/file-preview-actions.tsx +38 -0
  65. package/src/file-item/file-title.tsx +48 -0
  66. package/src/file-item/types.ts +27 -0
  67. package/src/file-item/use-register-in-image-gallery.tsx +70 -0
  68. package/src/file-item-2.tsx +32 -348
  69. package/src/file-item.tsx +6 -3
  70. package/src/hue-shift.test.ts +91 -0
  71. package/src/icons/ast/ChatBubble.ts +8 -0
  72. package/src/icons/ast/ChatFloat.ts +8 -0
  73. package/src/icons/ast/ChatSidebar.ts +8 -0
  74. package/src/icons/ast/FileCounter.ts +8 -0
  75. package/src/icons/ast/FileMultiple.ts +8 -0
  76. package/src/icons/ast/FileOther.ts +8 -0
  77. package/src/icons/ast/NoBorder.ts +8 -0
  78. package/src/icons/ast/NoFill.ts +8 -0
  79. package/src/icons/ast/ValueEdit.ts +8 -0
  80. package/src/icons/ast/index.tsx +9 -0
  81. package/src/icons/react/ChatBubble.tsx +13 -0
  82. package/src/icons/react/ChatFloat.tsx +13 -0
  83. package/src/icons/react/ChatSidebar.tsx +13 -0
  84. package/src/icons/react/FileCounter.tsx +13 -0
  85. package/src/icons/react/FileMultiple.tsx +13 -0
  86. package/src/icons/react/FileOther.tsx +13 -0
  87. package/src/icons/react/NoBorder.tsx +13 -0
  88. package/src/icons/react/NoFill.tsx +13 -0
  89. package/src/icons/react/ValueEdit.tsx +13 -0
  90. package/src/icons/react/index.tsx +9 -0
  91. package/src/icons/svg/chat-bubble.svg +4 -0
  92. package/src/icons/svg/chat-float.svg +4 -0
  93. package/src/icons/svg/chat-sidebar.svg +4 -0
  94. package/src/icons/svg/file-counter.svg +3 -0
  95. package/src/icons/svg/file-multiple.svg +3 -0
  96. package/src/icons/svg/file-other.svg +3 -0
  97. package/src/icons/svg/no-border.svg +17 -0
  98. package/src/icons/svg/no-fill.svg +4 -0
  99. package/src/icons/svg/value-edit.svg +3 -0
  100. package/src/images-gallery/images-gallery.tsx +8 -6
  101. package/src/images-gallery/slide-buttons.tsx +4 -11
  102. package/src/is-iOS.ts +0 -1
  103. package/src/is-in-popup.ts +2 -1
  104. package/src/lists/actions-menu-row-surface.tsx +7 -7
  105. package/src/loading-sausage.tsx +2 -2
  106. package/src/media-query-utils.ts +1 -2
  107. package/src/mobile-keyboard-aware-popup.tsx +6 -5
  108. package/src/modal-menu/modal-menu-content.tsx +2 -2
  109. package/src/number-input/decimal.js +9 -7
  110. package/src/number-input/{number-inline-input-with-autosize.tsx → number-input-inline-with-autosize.tsx} +20 -41
  111. package/src/number-input/number-input-inline.tsx +118 -0
  112. package/src/number-input/{index.test.js → number-input.test.tsx} +38 -43
  113. package/src/number-input/number-input.tsx +63 -121
  114. package/src/number-input/types.ts +19 -0
  115. package/src/number-input/utils.ts +61 -0
  116. package/src/online-users.tsx +2 -2
  117. package/src/palette-generator.test.ts +309 -0
  118. package/src/palette-generator.ts +160 -0
  119. package/src/palettes/_.ts +72 -0
  120. package/src/palettes/slate-arch.colors-snapshot.test.ts +1689 -0
  121. package/src/palettes/slate-arch.ts +188 -0
  122. package/src/palettes/slate-user.colors-snapshot.test.ts +1689 -0
  123. package/src/palettes/slate-user.ts +187 -0
  124. package/src/palettes/warm-arch.colors-snapshot.test.ts +1689 -0
  125. package/src/palettes/warm-arch.ts +222 -0
  126. package/src/palettes/warm-user.colors-snapshot.test.ts +1689 -0
  127. package/src/palettes/warm-user.ts +222 -0
  128. package/src/platform.ts +0 -3
  129. package/src/popover/get-element-ref.ts +28 -0
  130. package/src/popover/index.tsx +236 -339
  131. package/src/popover/{mobile-popup-context.tsx → mobile-popover-context.tsx} +7 -7
  132. package/src/popover/mobile-popover.tsx +169 -0
  133. package/src/popover/modifiers.tsx +2 -2
  134. package/src/popover/popup-stack-context.tsx +8 -9
  135. package/src/progress.tsx +2 -2
  136. package/src/reactions/reaction-button.tsx +12 -6
  137. package/src/root-theme-provider.test.tsx +411 -0
  138. package/src/scale-generator.ts +356 -0
  139. package/src/select/components/menu-list-virtualized.tsx +12 -25
  140. package/src/select/components/menu.tsx +12 -2
  141. package/src/select/index.tsx +6 -5
  142. package/src/select/select.tsx +38 -39
  143. package/src/select/styles.ts +0 -1
  144. package/src/select/util.ts +1 -1
  145. package/src/static-palettes.ts +356 -0
  146. package/src/thematic-color-picker.tsx +266 -0
  147. package/src/thematic-constants.tsx +27 -0
  148. package/src/thematic-controls.tsx +144 -0
  149. package/src/thematic-scales.tsx +122 -0
  150. package/src/thematic-state.ts +370 -0
  151. package/src/thematic.tsx +386 -0
  152. package/src/theme-provider.test.tsx +820 -0
  153. package/src/theme-provider.tsx +158 -92
  154. package/src/theme-settings.ts +67 -12
  155. package/src/theme-styles.ts +58 -6
  156. package/src/toast/toast-action.tsx +1 -1
  157. package/src/toast/toast.tsx +1 -2
  158. package/src/toggle-on-off.tsx +2 -2
  159. package/src/toggle.tsx +7 -8
  160. package/src/tooltip.tsx +14 -10
  161. package/src/type-badge.tsx +7 -14
  162. package/src/unit/styles.ts +2 -25
  163. package/src/unit/unit-with-tooltip.tsx +3 -2
  164. package/src/use-is-phone.tsx +7 -2
  165. package/src/use-long-press.tsx +2 -2
  166. package/src/use-on-screen-keyboard-data.tsx +2 -2
  167. package/src/with-data.tsx +4 -3
  168. package/src/workflow-progress-icon.tsx +2 -2
  169. package/.eslintignore +0 -3
  170. package/.eslintrc +0 -14
  171. package/src/__mocks__/createInlineTheme.js +0 -3
  172. package/src/create-inline-theme.ts +0 -66
  173. package/src/number-input/index.js +0 -191
  174. package/src/palette.ts +0 -237
@@ -1,8 +1,25 @@
1
1
  import invariant from "invariant";
2
- import {themeStyles} from "./theme-styles";
2
+ import {
3
+ chooseGlobalStyles,
4
+ defaultLightTheme,
5
+ defaultLightThemeStyles,
6
+ defaultPalette,
7
+ selectGlobalTheme,
8
+ } from "./theme-styles";
3
9
  import {layoutStyles} from "./layout-styles";
4
- import {createContext, ReactNode, useContext, useEffect, useLayoutEffect, useState} from "react";
5
- import {colors, getThemeColors, ThemeColors} from "./design-system";
10
+ import {
11
+ createContext,
12
+ forwardRef,
13
+ PropsWithChildren,
14
+ ReactNode,
15
+ useContext,
16
+ useEffect,
17
+ useLayoutEffect,
18
+ useMemo,
19
+ useState,
20
+ } from "react";
21
+ import {createInlineStyles, getThemeColors, ThemeColors, ThemeStyles} from "./design-system";
22
+ import {selectPalette, ThemePalette} from "./palette-generator";
6
23
  import {
7
24
  subscribeOnThemeMenuPreferenceChange,
8
25
  subscribeOnThemeModeChange,
@@ -10,19 +27,30 @@ import {
10
27
  ThemeMenuPreference,
11
28
  ThemeMode,
12
29
  ThemePreference,
30
+ useWarmPreference,
13
31
  } from "./theme-settings";
32
+ import {css} from "@linaria/core";
14
33
 
15
- const FiberyThemePreferenceContext = createContext<ThemePreference | null>(null);
16
- const FiberyThemeMenuPreferenceContext = createContext<ThemeMenuPreference | null>(null);
34
+ const ThemePreferenceContext = createContext<ThemePreference | null>(null);
35
+
36
+ export function useThemePreference(): ThemePreference {
37
+ const themePreference = useContext(ThemePreferenceContext);
38
+ invariant(themePreference, `please ensure the component is wrapped in a <RootThemeProvider>`);
39
+ return themePreference;
40
+ }
17
41
 
18
- const FiberyThemeContext = createContext<ThemeColors>(getThemeColors(colors.brandColors.blue, "light"));
42
+ const ThemeMenuPreferenceContext = createContext<ThemeMenuPreference | null>(null);
19
43
 
20
- function ThemeProvider({theme, children}: {theme: ThemeColors; children: ReactNode}): JSX.Element {
21
- return <FiberyThemeContext.Provider value={theme}>{children}</FiberyThemeContext.Provider>;
44
+ export function useThemeMenuPreference(): ThemeMenuPreference {
45
+ const themeMenuPreference = useContext(ThemeMenuPreferenceContext);
46
+ invariant(themeMenuPreference, `please ensure the component is wrapped in a <RootThemeProvider>`);
47
+ return themeMenuPreference;
22
48
  }
23
49
 
50
+ const ThemeColorsContext = createContext<ThemeColors>(defaultLightTheme);
51
+
24
52
  export function useTheme(): ThemeColors {
25
- return useContext(FiberyThemeContext);
53
+ return useContext(ThemeColorsContext);
26
54
  }
27
55
 
28
56
  export function useThemeMode(): ThemeMode {
@@ -30,63 +58,153 @@ export function useThemeMode(): ThemeMode {
30
58
  return theme.mode;
31
59
  }
32
60
 
33
- export function useThemePreference(): ThemePreference {
34
- const themePreference = useContext(FiberyThemePreferenceContext);
61
+ const RootThemeStylesContext = createContext<ThemeStyles>(defaultLightThemeStyles);
62
+ const ThemeStylesContext = createContext<ThemeStyles>(defaultLightThemeStyles);
35
63
 
36
- invariant(
37
- themePreference,
38
- "could not find theme preference context value; please ensure the component is wrapped in a <FiberyThemePreferenceContext>"
39
- );
64
+ export function useThemeStyles(): ThemeStyles {
65
+ return useContext(ThemeStylesContext);
66
+ }
40
67
 
41
- return themePreference;
68
+ const ThemePaletteContext = createContext<ThemePalette>(defaultPalette);
69
+
70
+ export function useThemePalette(): ThemePalette {
71
+ return useContext(ThemePaletteContext);
42
72
  }
43
73
 
44
- export function useThemeMenuPreference(): ThemeMenuPreference {
45
- const themeMenuPreference = useContext(FiberyThemeMenuPreferenceContext);
74
+ type ThematicContextValue = {
75
+ palette: ThemePalette;
76
+ setPalette: (palette: ThemePalette | null) => void;
77
+ };
46
78
 
47
- invariant(
48
- themeMenuPreference,
49
- "could not find theme menu preference context value; please ensure the component is wrapped in a <FiberyThemeMenuPreferenceContext>"
50
- );
79
+ const ThematicContext = createContext<ThematicContextValue>({
80
+ palette: defaultPalette,
81
+ setPalette: () => {},
82
+ });
51
83
 
52
- return themeMenuPreference;
84
+ export function useThematic(): ThematicContextValue {
85
+ return useContext(ThematicContext);
53
86
  }
54
87
 
55
- export function useMenuTheme() {
56
- const themeMode = useThemeMode();
57
- return getThemeColors(colors.brandColors.blue, themeMode === "light" ? "light" : "dark");
88
+ export function ThemeProvider({
89
+ theme: themeOverride,
90
+ color: colorOverride,
91
+ mode: modeOverride,
92
+ portal,
93
+ children,
94
+ }: PropsWithChildren<
95
+ {portal?: true} & (
96
+ | {theme: ThemeColors; color?: never; mode?: never}
97
+ | {color?: string; theme?: never; mode?: ThemeMode}
98
+ )
99
+ >) {
100
+ const palette = useContext(ThemePaletteContext);
101
+ const parentTheme = useContext(ThemeColorsContext);
102
+
103
+ const mode = modeOverride ?? parentTheme.mode;
104
+ const color = colorOverride ?? parentTheme.primary;
105
+ const theme = themeOverride ?? getThemeColors(color, mode, palette);
106
+
107
+ const parentStyles = useContext(portal ? RootThemeStylesContext : ThemeStylesContext);
108
+ const styles = createInlineStyles(theme);
109
+ const stylesDiff = useMemo(() => {
110
+ if (styles === parentStyles) {
111
+ return undefined;
112
+ }
113
+ const diff: ThemeStyles = {};
114
+ for (const key of Object.keys(styles)) {
115
+ if (styles[key] !== parentStyles[key]) {
116
+ diff[key] = styles[key];
117
+ }
118
+ }
119
+ return diff;
120
+ }, [styles, parentStyles]);
121
+
122
+ return (
123
+ <ThemeColorsContext.Provider value={theme}>
124
+ <ThemeStylesContext.Provider value={styles}>
125
+ <div
126
+ style={stylesDiff}
127
+ className={css`
128
+ display: contents;
129
+ `}
130
+ >
131
+ {children}
132
+ </div>
133
+ </ThemeStylesContext.Provider>
134
+ </ThemeColorsContext.Provider>
135
+ );
58
136
  }
59
137
 
138
+ /**
139
+ * Wrapper for ThemeProvider that absorbs Radix Portal's `asChild` ref/props.
140
+ * Use as direct child of Radix Portal components (MenuPortal, TooltipPortal, etc.)
141
+ */
142
+ export const RadixPortalThemeProvider = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(
143
+ function PortalThemeProvider({children, ...rest}, ref) {
144
+ return (
145
+ <div ref={ref} {...rest}>
146
+ <ThemeProvider portal>{children}</ThemeProvider>
147
+ </div>
148
+ );
149
+ }
150
+ );
151
+
60
152
  export function MenuThemeProvider({children}: {children: ReactNode}) {
61
- const theme = useMenuTheme();
62
- return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
153
+ const themeMode = useThemeMode();
154
+ // light2 = always dark menu
155
+ const mode = themeMode === "light" ? "light" : "dark";
156
+ return <ThemeProvider mode={mode}>{children}</ThemeProvider>;
63
157
  }
64
158
 
65
159
  export function RootThemeProvider({
66
160
  initialThemeMode,
67
161
  initialPreference,
68
162
  initialMenuPreference,
163
+ isArchitect = false,
69
164
  children,
70
165
  }: {
71
166
  initialThemeMode: ThemeMode;
72
167
  initialPreference: ThemePreference;
73
168
  initialMenuPreference: ThemeMenuPreference;
169
+ isArchitect?: boolean;
74
170
  children: ReactNode;
75
171
  }): JSX.Element {
76
172
  const [themeMode, setThemeMode] = useState<ThemeMode>(initialThemeMode);
173
+ useEffect(() => subscribeOnThemeModeChange(setThemeMode), []);
174
+
175
+ const [warm] = useWarmPreference();
176
+ useEffect(() => chooseGlobalStyles(themeMode, warm), [themeMode, warm]);
177
+
178
+ const [paletteOverride, setPaletteOverride] = useState<ThemePalette | null>(null);
179
+
180
+ const palette = useMemo(() => {
181
+ return paletteOverride ?? selectPalette({warmLook: warm, isArchitect});
182
+ }, [paletteOverride, warm, isArchitect]);
183
+
184
+ let res = <ThemeProvider theme={getThemeColors(null, themeMode, palette)}>{children}</ThemeProvider>;
185
+
186
+ // Set base context to what global CSS has, so ThemeProvider diffs against it
187
+ const [cssThemeColors, cssThemeStyles] = selectGlobalTheme(warm, themeMode === "dark");
188
+ res = <ThemeStylesContext.Provider value={cssThemeStyles}>{res}</ThemeStylesContext.Provider>;
189
+ res = <RootThemeStylesContext.Provider value={cssThemeStyles}>{res}</RootThemeStylesContext.Provider>;
190
+ res = <ThemeColorsContext.Provider value={cssThemeColors}>{res}</ThemeColorsContext.Provider>;
191
+
192
+ res = <ThemePaletteContext.Provider value={palette}>{res}</ThemePaletteContext.Provider>;
193
+
194
+ const thematicContextValue = useMemo<ThematicContextValue>(
195
+ () => ({palette, setPalette: setPaletteOverride}),
196
+ [palette, setPaletteOverride]
197
+ );
198
+ res = <ThematicContext.Provider value={thematicContextValue}>{res}</ThematicContext.Provider>;
199
+
77
200
  const [themePreference, setThemePreference] = useState<ThemePreference>(initialPreference);
201
+ useEffect(() => subscribeOnThemePreferenceChange(setThemePreference), []);
202
+ res = <ThemePreferenceContext.Provider value={themePreference}>{res}</ThemePreferenceContext.Provider>;
203
+
78
204
  const [themeMenuPreference, setThemeMenuPreference] = useState<ThemeMenuPreference>(initialMenuPreference);
79
- const theme = getThemeColors(colors.brandColors.blue, themeMode);
80
- useEffect(() => {
81
- const unsubscribeMode = subscribeOnThemeModeChange(setThemeMode);
82
- const unsubscribePreference = subscribeOnThemePreferenceChange(setThemePreference);
83
- const unsubscribeMenuPreference = subscribeOnThemeMenuPreferenceChange(setThemeMenuPreference);
84
- return () => {
85
- unsubscribeMode();
86
- unsubscribePreference();
87
- unsubscribeMenuPreference();
88
- };
89
- }, []);
205
+ useEffect(() => subscribeOnThemeMenuPreferenceChange(setThemeMenuPreference), []);
206
+ res = <ThemeMenuPreferenceContext.Provider value={themeMenuPreference}>{res}</ThemeMenuPreferenceContext.Provider>;
207
+
90
208
  useLayoutEffect(() => {
91
209
  const documentElement = document.documentElement;
92
210
  documentElement.classList.add(layoutStyles);
@@ -94,58 +212,6 @@ export function RootThemeProvider({
94
212
  documentElement.classList.remove(layoutStyles);
95
213
  };
96
214
  }, []);
97
- useEffect(() => {
98
- const darkThemeClassName = "dark-theme";
99
- const lightThemeAndDarkMenuClassName = "light-theme-and-dark-menu";
100
- const meta = document.querySelector('meta[name="color-scheme"]');
101
- let colorScheme = "light";
102
- const documentElement = document.documentElement;
103
- documentElement.classList.remove(darkThemeClassName);
104
- documentElement.classList.remove(lightThemeAndDarkMenuClassName);
105
- documentElement.classList.add(themeStyles);
106
- if (themeMode === "dark") {
107
- documentElement.classList.add(darkThemeClassName);
108
- colorScheme = "dark";
109
- } else if (themeMode === "light2") {
110
- documentElement.classList.add(lightThemeAndDarkMenuClassName);
111
- }
112
-
113
- documentElement.style.colorScheme = colorScheme;
114
- meta?.setAttribute("content", colorScheme);
115
215
 
116
- return () => {
117
- documentElement.classList.remove(darkThemeClassName);
118
- documentElement.classList.remove(lightThemeAndDarkMenuClassName);
119
- };
120
- }, [themeMode]);
121
-
122
- return (
123
- <FiberyThemePreferenceContext.Provider value={themePreference}>
124
- <FiberyThemeMenuPreferenceContext.Provider value={themeMenuPreference}>
125
- <ThemeProvider theme={theme}>{children}</ThemeProvider>
126
- </FiberyThemeMenuPreferenceContext.Provider>
127
- </FiberyThemePreferenceContext.Provider>
128
- );
129
- }
130
-
131
- interface WithThemeModeProps {
132
- themeMode: ThemeMode;
216
+ return res;
133
217
  }
134
-
135
- export function withThemeMode<T extends WithThemeModeProps = WithThemeModeProps>(
136
- WrappedComponent: React.ComponentType<T>
137
- ) {
138
- const displayName = WrappedComponent.displayName || WrappedComponent.name || "Component";
139
-
140
- const ComponentWithThemeMode = (props: Omit<T, keyof WithThemeModeProps>) => {
141
- const themeMode = useThemeMode();
142
-
143
- return <WrappedComponent {...(props as T)} themeMode={themeMode} />;
144
- };
145
-
146
- ComponentWithThemeMode.displayName = `withThemeMode(${displayName})`;
147
-
148
- return ComponentWithThemeMode;
149
- }
150
-
151
- export {ThemeProvider};
@@ -1,3 +1,4 @@
1
+ import {useEffect, useState} from "react";
1
2
  import {getMediaQueryList, subscribeOnChange as subscribeOnMediaQueryChange} from "./media-query-utils";
2
3
 
3
4
  const listenersThemeMode = new Map();
@@ -10,6 +11,7 @@ const themeMenuPreferenceKey = "theme-menu-preference";
10
11
  export type ThemePreference = "dark" | "light" | "auto";
11
12
  export type ThemeMenuPreference = "dark" | "auto";
12
13
 
14
+ export const themeModes = ["dark", "light", "light2"] as const;
13
15
  export type ThemeMode = "dark" | "light" | "light2";
14
16
 
15
17
  export function getThemePreference(): ThemePreference {
@@ -42,7 +44,7 @@ export function getThemeMenuPreference(): ThemeMenuPreference {
42
44
  return "auto";
43
45
  }
44
46
 
45
- export const alignTheme = (theme: ThemeMode) => {
47
+ export const alignTheme = (theme: ThemeMode): "light" | "dark" => {
46
48
  if (theme === "light2") {
47
49
  return "light";
48
50
  }
@@ -56,22 +58,18 @@ function getDarkMediaQuery() {
56
58
  }
57
59
 
58
60
  export function getThemeMode(): ThemeMode {
59
- const preference = getThemePreference();
60
- const menuPreference = getThemeMenuPreference();
61
-
62
- if (preference === "auto") {
63
- const matches = getDarkMediaQuery().matches;
64
- if (matches) {
61
+ const item = getThemePreference();
62
+ const menu = getThemeMenuPreference();
63
+ if (item === "auto") {
64
+ if (getDarkMediaQuery().matches) {
65
65
  return "dark";
66
- }
67
- if (menuPreference === "dark") {
66
+ } else if (menu === "dark") {
68
67
  return "light2";
69
68
  }
70
69
  return "light";
71
- } else if (preference === "dark") {
70
+ } else if (item === "dark") {
72
71
  return "dark";
73
- }
74
- if (menuPreference === "dark") {
72
+ } else if (menu === "dark") {
75
73
  return "light2";
76
74
  }
77
75
  return "light";
@@ -171,3 +169,60 @@ export function subscribeOnThemeModeChange(cb: (themeMode: ThemeMode) => void):
171
169
  window.removeEventListener("storage", storageListener);
172
170
  };
173
171
  }
172
+
173
+ const warmKey = "warm";
174
+ const warmListeners = new Set<(warm: boolean) => void>();
175
+
176
+ export function hasWarmPreference(): boolean {
177
+ try {
178
+ return localStorage.getItem(warmKey) !== null;
179
+ } catch {
180
+ return false;
181
+ }
182
+ }
183
+
184
+ export function loadWarmPreference(): boolean {
185
+ try {
186
+ return localStorage.getItem(warmKey) === "true";
187
+ } catch {
188
+ return false;
189
+ }
190
+ }
191
+
192
+ export function storeWarmPreference(warm: boolean) {
193
+ try {
194
+ localStorage.setItem(warmKey, JSON.stringify(warm));
195
+ } catch {
196
+ // ignore
197
+ }
198
+ warmListeners.forEach((cb) => cb(warm));
199
+ }
200
+
201
+ export function subscribeOnWarmPreferenceChange(cb: (warm: boolean) => void): () => void {
202
+ warmListeners.add(cb);
203
+ let prev = loadWarmPreference();
204
+ const storageListener = () => {
205
+ const next = loadWarmPreference();
206
+ if (prev !== next) {
207
+ prev = next;
208
+ cb(next);
209
+ }
210
+ };
211
+ window.addEventListener("storage", storageListener);
212
+ return () => {
213
+ warmListeners.delete(cb);
214
+ window.removeEventListener("storage", storageListener);
215
+ };
216
+ }
217
+
218
+ export function useWarmPreference() {
219
+ const [warm, setWarm] = useState(loadWarmPreference);
220
+ useEffect(() => subscribeOnWarmPreferenceChange(setWarm), []);
221
+ return [
222
+ warm,
223
+ (warm: boolean) => {
224
+ setWarm(warm);
225
+ storeWarmPreference(warm);
226
+ },
227
+ ] as const;
228
+ }
@@ -1,14 +1,66 @@
1
1
  import {css} from "@linaria/core";
2
- import {createInlineTheme, getThemeColors, colors} from "./design-system";
3
- const lightTheme = getThemeColors(colors.brandColors.blue, "light");
4
- const darkTheme = getThemeColors(colors.brandColors.blue, "dark");
2
+ import {createInlineStyles, getThemeColors, ThemeColors, ThemeStyles} from "./design-system";
3
+ import {warmUserPalette} from "./palettes/warm-user";
4
+ import {slateUserPalette} from "./palettes/slate-user";
5
+ import {ThemeMode} from "./theme-settings";
5
6
 
6
- export const themeStyles = css`
7
+ export const defaultPalette = slateUserPalette;
8
+ export const defaultLightTheme = getThemeColors(null, "light", defaultPalette);
9
+ export const defaultDarkTheme = getThemeColors(null, "dark", defaultPalette);
10
+
11
+ const warmPalette = warmUserPalette;
12
+ const warmLightTheme = getThemeColors(null, "light", warmPalette);
13
+ const warmDarkTheme = getThemeColors(null, "dark", warmPalette);
14
+
15
+ export const defaultLightThemeStyles = createInlineStyles(defaultLightTheme);
16
+ export const defaultDarkThemeStyles = createInlineStyles(defaultDarkTheme);
17
+
18
+ const warmLightThemeStyles = createInlineStyles(warmLightTheme);
19
+ const warmDarkThemeStyles = createInlineStyles(warmDarkTheme);
20
+
21
+ export function selectGlobalTheme(warm: boolean, dark: boolean): [colors: ThemeColors, styles: ThemeStyles] {
22
+ if (warm) {
23
+ return dark ? [warmDarkTheme, warmDarkThemeStyles] : [warmLightTheme, warmLightThemeStyles];
24
+ } else {
25
+ return dark ? [defaultDarkTheme, defaultDarkThemeStyles] : [defaultLightTheme, defaultLightThemeStyles];
26
+ }
27
+ }
28
+
29
+ const darkClassName = "dark-theme";
30
+ const light2ClassName = "light-theme-and-dark-menu";
31
+ const warmClassName = "warm-palette";
32
+
33
+ export function chooseGlobalStyles(themeMode: ThemeMode, warm: boolean) {
34
+ const htmlElement = document.documentElement;
35
+ htmlElement.classList.toggle(darkClassName, themeMode === "dark");
36
+ htmlElement.classList.toggle(light2ClassName, themeMode === "light2");
37
+ htmlElement.classList.toggle(warmClassName, warm);
38
+
39
+ const colorScheme = themeMode === "dark" ? "dark" : "light";
40
+ htmlElement.style.colorScheme = colorScheme;
41
+
42
+ // Multiple color schemes (FIXME: missing, unknown implications) "light" | "dark" | "light dark" | "dark light"
43
+ // indicates that the first scheme is preferred by the document, but that the second scheme is acceptable if the user prefers it.
44
+ // see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/name/color-scheme#usage_notes
45
+ const colorSchemeMeta = document.querySelector('meta[name="color-scheme"]');
46
+ colorSchemeMeta?.setAttribute("content", colorScheme);
47
+
48
+ htmlElement.classList.add(globals);
49
+ }
50
+
51
+ export const globals = css`
7
52
  :global() {
8
53
  html:root {
9
- ${createInlineTheme(lightTheme, [])}
54
+ ${defaultLightThemeStyles}
10
55
  &.dark-theme {
11
- ${createInlineTheme(darkTheme, [])}
56
+ ${defaultDarkThemeStyles}
57
+ }
58
+
59
+ &.warm-palette {
60
+ ${warmLightThemeStyles}
61
+ &.dark-theme {
62
+ ${warmDarkThemeStyles}
63
+ }
12
64
  }
13
65
  }
14
66
  }
@@ -19,7 +19,7 @@ export const ToastAction = forwardRef<
19
19
  ButtonProps & {altText: ToastActionProps["altText"]}
20
20
  >(({children, altText, ...props}, ref) => {
21
21
  const theme = useTheme();
22
- const buttonColors = theme.mode === "dark" ? makeButtonColors(theme.textColor, "outline") : {};
22
+ const buttonColors = theme.mode === "dark" ? makeButtonColors(theme.textColor, "outline", theme) : {};
23
23
  return (
24
24
  <RadixToastAction className={toastActionCss} ref={ref} altText={altText} asChild>
25
25
  <Button style={buttonColors} variant="outline" color="neutral" {...props}>
@@ -1,7 +1,7 @@
1
1
  import {useCallbackRef} from "@fibery/react/src/use-callback-ref";
2
2
  import {useControllableState} from "@fibery/react/src/use-controllable-state";
3
3
  import {css, cx} from "@linaria/core";
4
- import {useEffect, useRef, useCallback} from "react";
4
+ import {useCallback, useEffect, useRef} from "react";
5
5
  import {space, themeVars} from "../design-system";
6
6
  import Spinner from "../icons/react/Spinner";
7
7
  import CheckCircleFilled from "../icons/react/CheckCircleFilled";
@@ -115,7 +115,6 @@ export const Toast: React.FC<ToastProps> = (props) => {
115
115
  clearTimeout(emergencyTimeout);
116
116
  };
117
117
  }
118
- // eslint-disable-next-line @typescript-eslint/no-empty-function
119
118
  return () => {};
120
119
  }, [isOpen, onHideCb]);
121
120
 
@@ -1,12 +1,12 @@
1
- import chroma from "chroma-js";
2
1
  import {FC, ComponentProps, useMemo} from "react";
2
+ import {isValidColor} from "./color-utils";
3
3
  import {ThemeColors} from "./design-system";
4
4
  import {useTheme} from "./theme-provider";
5
5
  import {Toggle} from "./toggle";
6
6
  import {useIsPhone} from "./use-is-phone";
7
7
 
8
8
  const getBackgroundColors = (accentColor: string | undefined, theme: ThemeColors) => {
9
- return accentColor && chroma.valid(accentColor)
9
+ return accentColor && isValidColor(accentColor)
10
10
  ? {
11
11
  enabledColor: accentColor,
12
12
  disabledColor: theme.colorBgButtonSoftNeutralHover,
package/src/toggle.tsx CHANGED
@@ -1,11 +1,11 @@
1
1
  import {css, cx} from "@linaria/core";
2
2
  import {styled} from "@linaria/react";
3
- import chroma from "chroma-js";
4
3
  import {ComponentProps, FC, ReactNode} from "react";
5
4
  import {space, textStyles, transition} from "./design-system";
6
- import _ from "lodash";
5
+ import memoize from "lodash/memoize";
7
6
  import SpinnerIcon from "./icons/react/Spinner";
8
7
  import {mobileRootSelector} from "./mobile-styles";
8
+ import {darkenChroma, getAlpha, produceColor, setAlpha} from "./color-utils";
9
9
 
10
10
  const toggleIconHeightVar = "--fibery-toggle-icon-height";
11
11
  const toggleIconWidthVar = "--fibery-toggle-icon-width";
@@ -48,14 +48,12 @@ const labelWrapperStyle = css`
48
48
  align-items: center;
49
49
  `;
50
50
 
51
- const getOutlineColor = _.memoize((color: string) => {
52
- const alpha = chroma(color).alpha();
51
+ const getOutlineColor = memoize((color: string) => {
52
+ const alpha = getAlpha(color);
53
53
  if (alpha < 1) {
54
- return chroma(color)
55
- .alpha(alpha + 0.3)
56
- .css();
54
+ return produceColor(color, (c) => setAlpha(c, Math.min(1, alpha + 0.3)));
57
55
  } else {
58
- return chroma(color).darken(1).css();
56
+ return produceColor(color, (c) => darkenChroma(c, 1));
59
57
  }
60
58
  });
61
59
 
@@ -124,6 +122,7 @@ type ToggleProps = Omit<ComponentProps<"input">, "value" | "size"> & {
124
122
  size?: ToggleSize;
125
123
  };
126
124
 
125
+ /** @deprecated implementation detail */
127
126
  export const Toggle: FC<ToggleProps> = ({
128
127
  value,
129
128
  label,
package/src/tooltip.tsx CHANGED
@@ -2,7 +2,9 @@ import {css, cx} from "@linaria/core";
2
2
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
3
3
  import {forwardRef, type ReactNode, type SyntheticEvent} from "react";
4
4
  import {border, fontWeight, lineHeight, space, themeVars, tooltipDelay} from "./design-system";
5
+ import {RadixPortalThemeProvider} from "./theme-provider";
5
6
 
7
+ // FIXME consolidate as common util
6
8
  const isFlagPresent = (flag: string) => new RegExp(`[?&]${flag}\\b`).test(window.location.search);
7
9
 
8
10
  const preventDefaultAndStopPropagation = (e: SyntheticEvent) => {
@@ -204,16 +206,18 @@ export const Tooltip = forwardRef<HTMLButtonElement, TooltipProps>(
204
206
  {children}
205
207
  </TooltipPrimitive.Trigger>
206
208
  <TooltipPrimitive.Portal container={container}>
207
- <TooltipPrimitive.Content
208
- ref={setContentElement}
209
- side={side}
210
- sideOffset={sideOffset}
211
- align={align}
212
- alignOffset={alignOffset}
213
- className={cx(tooltipStyle, whiteBg && whiteBgStyle, tooltipContentStyle)}
214
- >
215
- {content}
216
- </TooltipPrimitive.Content>
209
+ <RadixPortalThemeProvider>
210
+ <TooltipPrimitive.Content
211
+ ref={setContentElement}
212
+ side={side}
213
+ sideOffset={sideOffset}
214
+ align={align}
215
+ alignOffset={alignOffset}
216
+ className={cx(tooltipStyle, whiteBg && whiteBgStyle, tooltipContentStyle)}
217
+ >
218
+ {content}
219
+ </TooltipPrimitive.Content>
220
+ </RadixPortalThemeProvider>
217
221
  </TooltipPrimitive.Portal>
218
222
  </TooltipPrimitive.Root>
219
223
  );
@@ -1,18 +1,17 @@
1
1
  import {css} from "@linaria/core";
2
2
  import cx from "classnames";
3
- import _ from "lodash";
3
+ import isString from "lodash/isString";
4
4
  import {CSSProperties, forwardRef, memo, ReactNode} from "react";
5
5
  import {a11yColor} from "./a11y-color";
6
6
  import {
7
7
  border,
8
- colors,
9
8
  fontWeight,
10
9
  getDarkenColor,
11
10
  getTextColor,
12
- getThemeValue,
13
11
  opacity,
14
12
  space,
15
13
  textStyles,
14
+ themeVars,
16
15
  transition,
17
16
  } from "./design-system";
18
17
  import {useTheme} from "./theme-provider";
@@ -180,7 +179,7 @@ export const TypeBadge = memo(
180
179
  ) {
181
180
  const TagName = onClick ? "button" : "div";
182
181
  const title =
183
- _.isString(children) && abbr ? (
182
+ isString(children) && abbr ? (
184
183
  noTooltip ? (
185
184
  makeAbbreviation(children)
186
185
  ) : (
@@ -196,13 +195,9 @@ export const TypeBadge = memo(
196
195
 
197
196
  const baseColor = color || theme.iconColor;
198
197
 
199
- const textBadgeColor = getThemeValue(theme.mode, {
200
- dark: a11yColor(getTextColor(baseColor), getDarkenColor(baseColor), 12),
201
- light: a11yColor(getTextColor(baseColor), getDarkenColor(baseColor), 12),
202
- light2: a11yColor(getTextColor(baseColor), getDarkenColor(baseColor), 12),
203
- });
198
+ const textBadgeColor = a11yColor(getTextColor(baseColor), getDarkenColor(baseColor), 12);
204
199
 
205
- const backgroundBadgeColor = getThemeValue(theme.mode, {
200
+ const backgroundBadgeColor = theme.select({
206
201
  dark: getDarkenColor(baseColor),
207
202
  light: baseColor,
208
203
  light2: baseColor,
@@ -210,10 +205,8 @@ export const TypeBadge = memo(
210
205
 
211
206
  const resultingStyle = {
212
207
  ...style,
213
- [colorProp]: baseColor ? textBadgeColor : colors.inversedTextColor,
214
- //[colorProp]: baseColor ? getTextColor(baseColor) : colors.inversedTextColor,
215
- [colorBackgroundProp]: backgroundBadgeColor || colors.shades.opacity25,
216
- //[colorBackgroundProp]: baseColor || colors.shades.opacity25,
208
+ [colorProp]: baseColor ? textBadgeColor : themeVars.inversedTextColor,
209
+ [colorBackgroundProp]: backgroundBadgeColor || themeVars.shades.opacity25,
217
210
  [colorBorderProp]: backgroundBadgeColor,
218
211
  [colorDarkProp]: getDarkenColor(baseColor),
219
212
  } as CSSProperties;