@fibery/ui-kit 1.40.4 → 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.
- package/CHANGELOG.md +6 -0
- package/eslint.config.mjs +16 -0
- package/package.json +8 -7
- package/src/a11y-color.test.ts +5 -24
- package/src/actions-menu/actions-menu-item.tsx +6 -9
- package/src/actions-menu/context-actions-menu.tsx +8 -3
- package/src/ai/model.tsx +0 -1
- package/src/ai/temperature.tsx +0 -1
- package/src/animated-height-container.tsx +3 -3
- package/src/antd/index.tsx +3 -0
- package/src/antd/input.tsx +0 -1
- package/src/antd/styles.ts +9 -9
- package/src/app-icon-with-fallback.tsx +5 -5
- package/src/app-icon-wrapper.tsx +2 -3
- package/src/app-icon.tsx +3 -3
- package/src/avatar.tsx +3 -3
- package/src/button/base-button.tsx +0 -1
- package/src/button/make-button-colors.ts +2 -2
- package/src/checkbox.tsx +1 -1
- package/src/collapsible-section.tsx +11 -1
- package/src/color-utils.test.ts +15 -25
- package/src/color-utils.ts +43 -8
- package/src/command-menu/index.tsx +1 -0
- package/src/context-menu/index.tsx +5 -5
- package/src/date-picker/contexts.ts +2 -2
- package/src/date-picker/date-range-picker.tsx +3 -4
- package/src/date-picker/single-date-picker.tsx +147 -119
- package/src/date-picker/types.ts +4 -3
- package/src/day-select/iso-week-day-select.tsx +2 -2
- package/src/day-select/week-day-select.tsx +2 -2
- package/src/delayed.tsx +0 -1
- package/src/design-system/alpha.test.ts +59 -0
- package/src/design-system/alpha.ts +41 -0
- package/src/design-system/animation.ts +10 -0
- package/src/design-system/colors-css.test.ts +39 -0
- package/src/design-system/colors-css.ts +25 -0
- package/src/design-system/colors-js.test.ts +232 -0
- package/src/design-system/colors-js.ts +107 -0
- package/src/design-system/colors.test.ts +74 -0
- package/src/{design-system.colors.ts → design-system/colors.ts} +133 -134
- package/src/design-system/date.ts +3 -0
- package/src/design-system/layout.ts +106 -0
- package/src/design-system/theme.test.ts +94 -0
- package/src/design-system/theme.ts +93 -0
- package/src/design-system/typography.ts +179 -0
- package/src/design-system/vars.test.ts +1679 -0
- package/src/design-system/vars.ts +52 -0
- package/src/design-system.test.ts +46 -303
- package/src/design-system.ts +25 -545
- package/src/dropdown-menu/index.tsx +5 -5
- package/src/emoji-picker/icon-emoji-picker.tsx +1 -1
- package/src/fibermoji-placeholder.tsx +3 -3
- package/src/field-container.tsx +11 -2
- package/src/file-item/file-icon.tsx +3 -3
- package/src/file-item-2.tsx +4 -10
- package/src/file-item.tsx +0 -1
- package/src/icons/ast/ChatBubble.ts +8 -0
- package/src/icons/ast/ChatFloat.ts +8 -0
- package/src/icons/ast/ChatSidebar.ts +8 -0
- package/src/icons/ast/FileOther.ts +8 -0
- package/src/icons/ast/NoBorder.ts +8 -0
- package/src/icons/ast/NoFill.ts +8 -0
- package/src/icons/ast/index.tsx +6 -0
- package/src/icons/react/ChatBubble.tsx +13 -0
- package/src/icons/react/ChatFloat.tsx +13 -0
- package/src/icons/react/ChatSidebar.tsx +13 -0
- package/src/icons/react/FileOther.tsx +13 -0
- package/src/icons/react/NoBorder.tsx +13 -0
- package/src/icons/react/NoFill.tsx +13 -0
- package/src/icons/react/index.tsx +6 -0
- package/src/icons/svg/chat-bubble.svg +4 -0
- package/src/icons/svg/chat-float.svg +4 -0
- package/src/icons/svg/chat-sidebar.svg +4 -0
- package/src/icons/svg/file-other.svg +3 -0
- package/src/icons/svg/no-border.svg +17 -0
- package/src/icons/svg/no-fill.svg +4 -0
- package/src/images-gallery/images-gallery.tsx +8 -6
- package/src/images-gallery/slide-buttons.tsx +2 -2
- package/src/is-iOS.ts +0 -1
- package/src/is-in-popup.ts +2 -1
- package/src/lists/actions-menu-row-surface.tsx +4 -4
- package/src/loading-sausage.tsx +2 -2
- package/src/media-query-utils.ts +1 -2
- package/src/mobile-keyboard-aware-popup.tsx +2 -2
- package/src/modal-menu/modal-menu-content.tsx +2 -2
- package/src/number-input/decimal.js +9 -7
- package/src/number-input/{number-inline-input-with-autosize.tsx → number-input-inline-with-autosize.tsx} +20 -41
- package/src/number-input/number-input-inline.tsx +118 -0
- package/src/number-input/{index.test.js → number-input.test.tsx} +38 -43
- package/src/number-input/number-input.tsx +63 -121
- package/src/number-input/types.ts +19 -0
- package/src/number-input/utils.ts +61 -0
- package/src/online-users.tsx +2 -2
- package/src/palette-generator.test.ts +24 -281
- package/src/palette-generator.ts +39 -45
- package/src/palettes/_.ts +72 -0
- package/src/palettes/slate-arch.colors-snapshot.test.ts +1689 -0
- package/src/palettes/slate-arch.ts +188 -0
- package/src/palettes/slate-user.colors-snapshot.test.ts +1689 -0
- package/src/palettes/slate-user.ts +187 -0
- package/src/palettes/warm-arch.colors-snapshot.test.ts +1689 -0
- package/src/palettes/warm-arch.ts +222 -0
- package/src/palettes/warm-user.colors-snapshot.test.ts +1689 -0
- package/src/palettes/warm-user.ts +222 -0
- package/src/popover/get-element-ref.ts +28 -0
- package/src/popover/index.tsx +232 -333
- package/src/popover/{mobile-popup-context.tsx → mobile-popover-context.tsx} +7 -7
- package/src/popover/mobile-popover.tsx +169 -0
- package/src/popover/modifiers.tsx +2 -2
- package/src/popover/popup-stack-context.tsx +8 -9
- package/src/root-theme-provider.test.tsx +114 -19
- package/src/scale-generator.ts +31 -22
- package/src/select/components/menu-list-virtualized.tsx +5 -3
- package/src/select/index.tsx +6 -5
- package/src/select/select.tsx +36 -38
- package/src/select/util.ts +1 -1
- package/src/static-palettes.ts +221 -11
- package/src/thematic-controls.tsx +6 -6
- package/src/thematic-scales.tsx +15 -15
- package/src/thematic-state.ts +57 -20
- package/src/thematic.tsx +36 -32
- package/src/theme-provider.test.tsx +31 -19
- package/src/theme-provider.tsx +41 -38
- package/src/theme-settings.ts +66 -11
- package/src/theme-styles.ts +53 -8
- package/src/toast/toast.tsx +1 -2
- package/src/toggle.tsx +2 -2
- package/src/tooltip.tsx +4 -3
- package/src/type-badge.tsx +4 -11
- package/src/unit/styles.ts +0 -23
- package/src/use-is-phone.tsx +7 -2
- package/src/use-on-screen-keyboard-data.tsx +2 -2
- package/src/with-data.tsx +4 -3
- package/src/workflow-progress-icon.tsx +2 -2
- package/.eslintignore +0 -3
- package/.eslintrc +0 -14
- package/src/__mocks__/createInlineTheme.js +0 -3
- package/src/__snapshots__/design-system.test.ts.snap +0 -7265
- package/src/create-inline-theme.ts +0 -67
- package/src/number-input/index.js +0 -191
- package/src/palette.ts +0 -253
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import noop from "lodash/noop";
|
|
2
2
|
import {FC, ReactNode, useState, useMemo} from "react";
|
|
3
3
|
import {createContext} from "@fibery/react/src/create-context";
|
|
4
4
|
|
|
5
|
-
type
|
|
5
|
+
type MobilePopoverContext = {
|
|
6
6
|
title?: string;
|
|
7
7
|
headerSlot?: ReactNode;
|
|
8
8
|
setTitle: (title: string) => void;
|
|
9
9
|
setHeader: (header?: ReactNode) => void;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
const [Provider,
|
|
13
|
-
setTitle:
|
|
14
|
-
setHeader:
|
|
12
|
+
const [Provider, useMobilePopover] = createContext<MobilePopoverContext>("MobilePopoverContext", {
|
|
13
|
+
setTitle: noop,
|
|
14
|
+
setHeader: noop,
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
export const
|
|
17
|
+
export const MobilePopoverProvider: FC<{children?: ReactNode}> = ({children}) => {
|
|
18
18
|
const [title, setTitle] = useState<string | undefined>();
|
|
19
19
|
const [headerSlot, setHeader] = useState<ReactNode | undefined>();
|
|
20
20
|
|
|
@@ -26,4 +26,4 @@ export const MobilePopupProvider: FC<{children?: ReactNode}> = ({children}) => {
|
|
|
26
26
|
return <Provider value={contextValue}>{children}</Provider>;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
export {
|
|
29
|
+
export {useMobilePopover};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {stopPropagation} from "@fibery/react/src/stop-propagation";
|
|
2
|
+
import {css} from "@linaria/core";
|
|
3
|
+
import cx from "classnames";
|
|
4
|
+
import {CSSProperties, ReactNode} from "react";
|
|
5
|
+
import {
|
|
6
|
+
safeAreaInsetBottomVar,
|
|
7
|
+
safeAreaInsetLeftVar,
|
|
8
|
+
safeAreaInsetRightVar,
|
|
9
|
+
safeAreaInsetTopVar,
|
|
10
|
+
} from "../mobile-styles";
|
|
11
|
+
import {space, textStyles, themeVars, transition} from "../design-system";
|
|
12
|
+
import {IconButton} from "../button/icon-button";
|
|
13
|
+
import CloseIcon from "../icons/react/Close";
|
|
14
|
+
import ArrowLeftIcon from "../icons/react/ArrowLeft";
|
|
15
|
+
import {ThemeProvider} from "../theme-provider";
|
|
16
|
+
import {useMobilePopover} from "./mobile-popover-context";
|
|
17
|
+
|
|
18
|
+
const mobileAnimationClassName = css`
|
|
19
|
+
transition: transform ${transition}, opacity ${transition};
|
|
20
|
+
transform: translateY(0);
|
|
21
|
+
opacity: 1;
|
|
22
|
+
|
|
23
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
24
|
+
@starting-style {
|
|
25
|
+
transform: translateY(20%);
|
|
26
|
+
opacity: 0;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
const popupContentClassName = css`
|
|
32
|
+
display: grid;
|
|
33
|
+
background: ${themeVars.actionMenuBg};
|
|
34
|
+
grid-template-rows: 60px 1fr;
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
export const mobileZIndex = 1002;
|
|
38
|
+
|
|
39
|
+
export function MobilePopover({
|
|
40
|
+
content,
|
|
41
|
+
mobilePopupStyle,
|
|
42
|
+
mobilePopupContentClassName,
|
|
43
|
+
title,
|
|
44
|
+
onClose,
|
|
45
|
+
height,
|
|
46
|
+
onNavigateBack,
|
|
47
|
+
}: {
|
|
48
|
+
content: ReactNode;
|
|
49
|
+
mobilePopupStyle?: CSSProperties;
|
|
50
|
+
mobilePopupContentClassName?: string;
|
|
51
|
+
title: string;
|
|
52
|
+
height?: string;
|
|
53
|
+
onClose?: () => void;
|
|
54
|
+
onNavigateBack?: () => void;
|
|
55
|
+
}) {
|
|
56
|
+
const {title: contextTitle, headerSlot} = useMobilePopover();
|
|
57
|
+
|
|
58
|
+
const headerContent = headerSlot ?? (
|
|
59
|
+
<>
|
|
60
|
+
{onNavigateBack && (
|
|
61
|
+
<IconButton onClick={onNavigateBack} size="xLarge">
|
|
62
|
+
<ArrowLeftIcon />
|
|
63
|
+
</IconButton>
|
|
64
|
+
)}
|
|
65
|
+
<div
|
|
66
|
+
className={css`
|
|
67
|
+
${textStyles.heading3}
|
|
68
|
+
flex: 1;
|
|
69
|
+
min-width: 0;
|
|
70
|
+
overflow: hidden;
|
|
71
|
+
text-overflow: ellipsis;
|
|
72
|
+
white-space: nowrap;
|
|
73
|
+
`}
|
|
74
|
+
>
|
|
75
|
+
{contextTitle ?? title}
|
|
76
|
+
</div>
|
|
77
|
+
{onClose && (
|
|
78
|
+
<IconButton onClick={onClose} size="xLarge">
|
|
79
|
+
<CloseIcon />
|
|
80
|
+
</IconButton>
|
|
81
|
+
)}
|
|
82
|
+
</>
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const popupContent = (
|
|
86
|
+
<>
|
|
87
|
+
<div
|
|
88
|
+
className={cx(
|
|
89
|
+
css`
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: ${space.s8}px;
|
|
93
|
+
padding: ${space.s8}px ${space.s8}px ${space.s8}px ${space.s20}px;
|
|
94
|
+
min-width: 0;
|
|
95
|
+
`,
|
|
96
|
+
onNavigateBack &&
|
|
97
|
+
css`
|
|
98
|
+
padding-left: ${space.s4}px;
|
|
99
|
+
`
|
|
100
|
+
)}
|
|
101
|
+
>
|
|
102
|
+
{headerContent}
|
|
103
|
+
</div>
|
|
104
|
+
<div
|
|
105
|
+
className={cx(
|
|
106
|
+
css`
|
|
107
|
+
border-top: ${themeVars.separators.opacity5};
|
|
108
|
+
min-width: 0;
|
|
109
|
+
`,
|
|
110
|
+
mobilePopupContentClassName
|
|
111
|
+
)}
|
|
112
|
+
>
|
|
113
|
+
{content}
|
|
114
|
+
</div>
|
|
115
|
+
</>
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const res = (
|
|
119
|
+
<div
|
|
120
|
+
className={cx(
|
|
121
|
+
css`
|
|
122
|
+
position: absolute;
|
|
123
|
+
left: var(${safeAreaInsetLeftVar});
|
|
124
|
+
right: var(${safeAreaInsetRightVar});
|
|
125
|
+
top: var(${safeAreaInsetTopVar});
|
|
126
|
+
bottom: var(${safeAreaInsetBottomVar});
|
|
127
|
+
z-index: ${mobileZIndex};
|
|
128
|
+
`,
|
|
129
|
+
!height && mobileAnimationClassName,
|
|
130
|
+
!height && popupContentClassName,
|
|
131
|
+
height &&
|
|
132
|
+
css`
|
|
133
|
+
display: grid;
|
|
134
|
+
`
|
|
135
|
+
)}
|
|
136
|
+
style={{
|
|
137
|
+
...mobilePopupStyle,
|
|
138
|
+
...(height ? {gridTemplateRows: `1fr ${height}`} : {}),
|
|
139
|
+
}}
|
|
140
|
+
onClick={stopPropagation}
|
|
141
|
+
>
|
|
142
|
+
{height ? (
|
|
143
|
+
<>
|
|
144
|
+
<div
|
|
145
|
+
onClick={onClose}
|
|
146
|
+
className={css`
|
|
147
|
+
background-color: ${themeVars.modalBg};
|
|
148
|
+
`}
|
|
149
|
+
/>
|
|
150
|
+
<div
|
|
151
|
+
className={cx(
|
|
152
|
+
css`
|
|
153
|
+
display: grid;
|
|
154
|
+
grid-template-rows: 60px 1fr;
|
|
155
|
+
`,
|
|
156
|
+
mobileAnimationClassName,
|
|
157
|
+
popupContentClassName
|
|
158
|
+
)}
|
|
159
|
+
>
|
|
160
|
+
{popupContent}
|
|
161
|
+
</div>
|
|
162
|
+
</>
|
|
163
|
+
) : (
|
|
164
|
+
popupContent
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
return <ThemeProvider portal>{res}</ThemeProvider>;
|
|
169
|
+
}
|
|
@@ -3,7 +3,7 @@ import {Modifier, ModifierArguments, Placement, Boundary, RootBoundary, Padding,
|
|
|
3
3
|
import maxSize from "popper-max-size-modifier";
|
|
4
4
|
import {getResizeListener} from "@fibery/helpers/utils/resize-listener";
|
|
5
5
|
import {isElementReference} from "./reference";
|
|
6
|
-
import
|
|
6
|
+
import noop from "lodash/noop";
|
|
7
7
|
|
|
8
8
|
type MaxSizeOptions = {
|
|
9
9
|
placement: Placement;
|
|
@@ -65,6 +65,6 @@ export const observeWidth: Modifier<"observeWidth", Record<string, never>> = {
|
|
|
65
65
|
name: "observeWidth",
|
|
66
66
|
enabled: true,
|
|
67
67
|
phase: "main",
|
|
68
|
-
fn:
|
|
68
|
+
fn: noop,
|
|
69
69
|
effect: observeWidthEffect,
|
|
70
70
|
};
|
|
@@ -25,17 +25,16 @@ const [Provider, usePopupStackContext] = createContext<{
|
|
|
25
25
|
|
|
26
26
|
export function PopupStackContextProvider({children}: {children: ReactNode}) {
|
|
27
27
|
useEffect(() => {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
});
|
|
28
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
29
|
+
if (event.key === "Escape") {
|
|
30
|
+
const callbacks = [...callbacksMap.values()].filter(Boolean);
|
|
31
|
+
const topmost = callbacks[callbacks.length - 1];
|
|
32
|
+
topmost?.(event);
|
|
33
|
+
}
|
|
35
34
|
};
|
|
36
|
-
document.addEventListener("keydown",
|
|
35
|
+
document.addEventListener("keydown", onKeyDown);
|
|
37
36
|
return () => {
|
|
38
|
-
document.removeEventListener("keydown",
|
|
37
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
39
38
|
};
|
|
40
39
|
}, []);
|
|
41
40
|
return <Provider value={value}>{children}</Provider>;
|
|
@@ -8,13 +8,10 @@ import {
|
|
|
8
8
|
useThemeMode,
|
|
9
9
|
useThemePreference,
|
|
10
10
|
} from "./theme-provider";
|
|
11
|
-
import {
|
|
12
|
-
import {defaultDarkTheme, defaultLightTheme} from "./theme-styles";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
jest.unmock("./create-inline-theme");
|
|
16
|
-
|
|
17
|
-
const palette = makeDefaultPalette();
|
|
11
|
+
import type {ThemePalette} from "./palette-generator";
|
|
12
|
+
import {defaultDarkTheme, defaultLightTheme, defaultPalette} from "./theme-styles";
|
|
13
|
+
import {createInlineStyles, getThemeColors} from "./design-system";
|
|
14
|
+
import {storeWarmPreference} from "./theme-settings";
|
|
18
15
|
|
|
19
16
|
function ThemeDisplay({testId = "theme-display"}: {testId?: string}) {
|
|
20
17
|
const theme = useTheme();
|
|
@@ -49,6 +46,7 @@ describe("RootThemeProvider", () => {
|
|
|
49
46
|
beforeEach(() => {
|
|
50
47
|
document.documentElement.className = "";
|
|
51
48
|
document.documentElement.style.colorScheme = "";
|
|
49
|
+
localStorage.clear();
|
|
52
50
|
});
|
|
53
51
|
|
|
54
52
|
describe("context provision", () => {
|
|
@@ -200,9 +198,9 @@ describe("RootThemeProvider", () => {
|
|
|
200
198
|
|
|
201
199
|
describe("diff optimization integrity", () => {
|
|
202
200
|
it("RootThemeProvider internal ThemeProvider only sets diff from base", () => {
|
|
203
|
-
const lightStyles =
|
|
204
|
-
const lightTheme = getThemeColors(null, "light",
|
|
205
|
-
const actualStyles =
|
|
201
|
+
const lightStyles = createInlineStyles(defaultLightTheme);
|
|
202
|
+
const lightTheme = getThemeColors(null, "light", defaultPalette);
|
|
203
|
+
const actualStyles = createInlineStyles(lightTheme);
|
|
206
204
|
|
|
207
205
|
// Count expected diff (theme from getThemeColors vs defaultLightTheme)
|
|
208
206
|
let expectedDiffCount = 0;
|
|
@@ -248,7 +246,7 @@ describe("RootThemeProvider", () => {
|
|
|
248
246
|
|
|
249
247
|
describe("setPalette", () => {
|
|
250
248
|
it("setPalette updates theme colors", () => {
|
|
251
|
-
let capturedSetPalette: (p: ThemePalette) => void = () => {};
|
|
249
|
+
let capturedSetPalette: (p: ThemePalette | null) => void = () => {};
|
|
252
250
|
|
|
253
251
|
function ThemeCapture() {
|
|
254
252
|
const {setPalette} = useThematic();
|
|
@@ -265,10 +263,8 @@ describe("RootThemeProvider", () => {
|
|
|
265
263
|
|
|
266
264
|
const initialCardBg = screen.getByTestId("capture").getAttribute("data-card-bg");
|
|
267
265
|
|
|
268
|
-
// cardBg in light mode = white
|
|
269
|
-
const newPalette =
|
|
270
|
-
newPalette.key = "test-palette"; // must change key - getThemeColors is memoized by key
|
|
271
|
-
newPalette.white = {...newPalette.white, 0: "oklch(0.9 0.05 200)"};
|
|
266
|
+
// cardBg in light mode = white, so modify white
|
|
267
|
+
const newPalette = {...defaultPalette, key: "test-palette", white: "oklch(0.9 0.05 200)"};
|
|
272
268
|
|
|
273
269
|
act(() => capturedSetPalette(newPalette));
|
|
274
270
|
|
|
@@ -278,7 +274,7 @@ describe("RootThemeProvider", () => {
|
|
|
278
274
|
});
|
|
279
275
|
|
|
280
276
|
it("nested ThemeProvider inherits updated palette", () => {
|
|
281
|
-
let capturedSetPalette: (p: ThemePalette) => void = () => {};
|
|
277
|
+
let capturedSetPalette: (p: ThemePalette | null) => void = () => {};
|
|
282
278
|
|
|
283
279
|
function PaletteControl() {
|
|
284
280
|
const {setPalette} = useThematic();
|
|
@@ -302,9 +298,7 @@ describe("RootThemeProvider", () => {
|
|
|
302
298
|
|
|
303
299
|
const initialCardBg = screen.getByTestId("nested").getAttribute("data-card-bg");
|
|
304
300
|
|
|
305
|
-
const newPalette =
|
|
306
|
-
newPalette.key = "test-palette-2";
|
|
307
|
-
newPalette.white = {...newPalette.white, 0: "oklch(0.8 0.02 100)"};
|
|
301
|
+
const newPalette = {...defaultPalette, key: "test-palette-2", white: "oklch(0.8 0.02 100)"};
|
|
308
302
|
|
|
309
303
|
act(() => capturedSetPalette(newPalette));
|
|
310
304
|
|
|
@@ -312,5 +306,106 @@ describe("RootThemeProvider", () => {
|
|
|
312
306
|
expect(updatedCardBg).toBe("oklch(0.8 0.02 100)");
|
|
313
307
|
expect(updatedCardBg).not.toBe(initialCardBg);
|
|
314
308
|
});
|
|
309
|
+
|
|
310
|
+
it("setPalette(null) resets to default palette", () => {
|
|
311
|
+
let capturedSetPalette: (p: ThemePalette | null) => void = () => {};
|
|
312
|
+
|
|
313
|
+
function ThemeCapture() {
|
|
314
|
+
const {setPalette} = useThematic();
|
|
315
|
+
const theme = useTheme();
|
|
316
|
+
capturedSetPalette = setPalette;
|
|
317
|
+
return <div data-testid="capture" data-card-bg={theme.cardBg} />;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
render(
|
|
321
|
+
<RootThemeProvider initialThemeMode="light" initialPreference="light" initialMenuPreference="auto">
|
|
322
|
+
<ThemeCapture />
|
|
323
|
+
</RootThemeProvider>
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const initialCardBg = screen.getByTestId("capture").getAttribute("data-card-bg");
|
|
327
|
+
|
|
328
|
+
const newPalette = {...defaultPalette, key: "test-override", white: "oklch(0.9 0.05 200)"};
|
|
329
|
+
act(() => capturedSetPalette(newPalette));
|
|
330
|
+
expect(screen.getByTestId("capture").getAttribute("data-card-bg")).toBe("oklch(0.9 0.05 200)");
|
|
331
|
+
|
|
332
|
+
act(() => capturedSetPalette(null));
|
|
333
|
+
expect(screen.getByTestId("capture").getAttribute("data-card-bg")).toBe(initialCardBg);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("override palette takes precedence over warm preference", () => {
|
|
337
|
+
let capturedSetPalette: (p: ThemePalette | null) => void = () => {};
|
|
338
|
+
|
|
339
|
+
function PaletteKeyDisplay() {
|
|
340
|
+
const {palette, setPalette} = useThematic();
|
|
341
|
+
capturedSetPalette = setPalette;
|
|
342
|
+
return <div data-testid="palette-key" data-key={palette.key} />;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
render(
|
|
346
|
+
<RootThemeProvider initialThemeMode="light" initialPreference="light" initialMenuPreference="auto">
|
|
347
|
+
<PaletteKeyDisplay />
|
|
348
|
+
</RootThemeProvider>
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const overridePalette = {...defaultPalette, key: "custom-override"};
|
|
352
|
+
act(() => capturedSetPalette(overridePalette));
|
|
353
|
+
expect(screen.getByTestId("palette-key").getAttribute("data-key")).toBe("custom-override");
|
|
354
|
+
|
|
355
|
+
// Changing warm preference doesn't affect override
|
|
356
|
+
act(() => storeWarmPreference(true));
|
|
357
|
+
expect(screen.getByTestId("palette-key").getAttribute("data-key")).toBe("custom-override");
|
|
358
|
+
|
|
359
|
+
// Clearing override picks up current warm preference
|
|
360
|
+
act(() => capturedSetPalette(null));
|
|
361
|
+
expect(screen.getByTestId("palette-key").getAttribute("data-key")).toBe("warm-user");
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
describe("warm preference / isArchitect", () => {
|
|
366
|
+
function PaletteKeyDisplay() {
|
|
367
|
+
const {palette} = useThematic();
|
|
368
|
+
return <div data-testid="palette-key" data-key={palette.key} />;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
it("uses default palette when warm is off and not architect", () => {
|
|
372
|
+
render(
|
|
373
|
+
<RootThemeProvider initialThemeMode="light" initialPreference="light" initialMenuPreference="auto">
|
|
374
|
+
<PaletteKeyDisplay />
|
|
375
|
+
</RootThemeProvider>
|
|
376
|
+
);
|
|
377
|
+
expect(screen.getByTestId("palette-key").getAttribute("data-key")).toBe("default");
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("uses warm palette when warm preference is stored", () => {
|
|
381
|
+
storeWarmPreference(true);
|
|
382
|
+
render(
|
|
383
|
+
<RootThemeProvider initialThemeMode="light" initialPreference="light" initialMenuPreference="auto">
|
|
384
|
+
<PaletteKeyDisplay />
|
|
385
|
+
</RootThemeProvider>
|
|
386
|
+
);
|
|
387
|
+
expect(screen.getByTestId("palette-key").getAttribute("data-key")).toBe("warm-user");
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("uses architect palette when isArchitect is true", () => {
|
|
391
|
+
render(
|
|
392
|
+
<RootThemeProvider initialThemeMode="light" initialPreference="light" initialMenuPreference="auto" isArchitect>
|
|
393
|
+
<PaletteKeyDisplay />
|
|
394
|
+
</RootThemeProvider>
|
|
395
|
+
);
|
|
396
|
+
expect(screen.getByTestId("palette-key").getAttribute("data-key")).toBe("slate-arch");
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("reacts to warm preference change at runtime", () => {
|
|
400
|
+
render(
|
|
401
|
+
<RootThemeProvider initialThemeMode="light" initialPreference="light" initialMenuPreference="auto" isArchitect>
|
|
402
|
+
<PaletteKeyDisplay />
|
|
403
|
+
</RootThemeProvider>
|
|
404
|
+
);
|
|
405
|
+
expect(screen.getByTestId("palette-key").getAttribute("data-key")).toBe("slate-arch");
|
|
406
|
+
|
|
407
|
+
act(() => storeWarmPreference(true));
|
|
408
|
+
expect(screen.getByTestId("palette-key").getAttribute("data-key")).toBe("warm-arch");
|
|
409
|
+
});
|
|
315
410
|
});
|
|
316
411
|
});
|
package/src/scale-generator.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {fromOKLCH} from "./color-utils";
|
|
2
2
|
import type {ColorScale} from "./palette-generator";
|
|
3
3
|
|
|
4
|
-
export type
|
|
5
|
-
base00:
|
|
4
|
+
export type OneBasedScale<T> = readonly [
|
|
5
|
+
base00: null,
|
|
6
6
|
step01: T,
|
|
7
7
|
step02: T,
|
|
8
8
|
step03: T,
|
|
@@ -17,7 +17,7 @@ export type BasedScale12<T> = readonly [
|
|
|
17
17
|
step12: T
|
|
18
18
|
];
|
|
19
19
|
|
|
20
|
-
export type
|
|
20
|
+
export type ZeroBasedScale<T> = readonly [
|
|
21
21
|
step01: T,
|
|
22
22
|
step02: T,
|
|
23
23
|
step03: T,
|
|
@@ -32,9 +32,13 @@ export type Scale12<T> = readonly [
|
|
|
32
32
|
step12: T
|
|
33
33
|
];
|
|
34
34
|
|
|
35
|
+
export function zeroBasedFromOneBased<T>(scale: OneBasedScale<T>) {
|
|
36
|
+
return scale.slice(1) as unknown as ZeroBasedScale<T>;
|
|
37
|
+
}
|
|
38
|
+
|
|
35
39
|
export interface ScaleCurves {
|
|
36
|
-
lightness:
|
|
37
|
-
chroma:
|
|
40
|
+
lightness: ZeroBasedScale<number>; // 0-100 scale
|
|
41
|
+
chroma: ZeroBasedScale<number>; // multipliers relative to peakChroma
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
export interface ScaleConfig {
|
|
@@ -155,7 +159,7 @@ function applyContrast(L: number, contrast: number): number {
|
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
export function generateColorScale(config: ScaleConfig): ColorScale {
|
|
158
|
-
const {hue, chroma, mode, curves, contrast = 1
|
|
162
|
+
const {hue, chroma, mode, curves, contrast = 1} = config;
|
|
159
163
|
|
|
160
164
|
const defaultCurves = mode === "light" ? DEFAULT_LIGHT_CURVES : DEFAULT_DARK_CURVES;
|
|
161
165
|
const {lightness, chroma: chromaCurve} = curves ?? defaultCurves;
|
|
@@ -166,7 +170,7 @@ export function generateColorScale(config: ScaleConfig): ColorScale {
|
|
|
166
170
|
return fromOKLCH(adjustedL / 100, C, hue);
|
|
167
171
|
});
|
|
168
172
|
|
|
169
|
-
return [
|
|
173
|
+
return [null, ...steps] as unknown as ColorScale;
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
/**
|
|
@@ -202,12 +206,10 @@ export function generateGrayScale(config: {
|
|
|
202
206
|
chroma?: number;
|
|
203
207
|
mode: "light" | "dark";
|
|
204
208
|
contrast?: number;
|
|
205
|
-
base?: string;
|
|
206
209
|
}): ColorScale {
|
|
207
|
-
const {hue = 0, chroma = 0, mode, contrast
|
|
210
|
+
const {hue = 0, chroma = 0, mode, contrast} = config;
|
|
208
211
|
const curves = mode === "light" ? GRAY_CURVES : GRAY_DARK_CURVES;
|
|
209
|
-
|
|
210
|
-
return generateColorScale({hue, chroma, mode, contrast, curves, base: base ?? defaultBase});
|
|
212
|
+
return generateColorScale({hue, chroma, mode, contrast, curves});
|
|
211
213
|
}
|
|
212
214
|
|
|
213
215
|
// ============ HUE INTERPOLATION ============
|
|
@@ -329,19 +331,26 @@ export function paletteFromHueBoth(hue: number): {light: ColorScale; dark: Color
|
|
|
329
331
|
};
|
|
330
332
|
}
|
|
331
333
|
|
|
332
|
-
|
|
333
|
-
const BLACK_ALPHAS: BasedScale12<number> = [1, 0.01, 0.03, 0.05, 0.07, 0.09, 0.11, 0.14, 0.22, 0.44, 0.48, 0.57, 0.91];
|
|
334
|
-
|
|
335
|
-
function alphaScale(L: number, C: number, H: number, alphas: BasedScale12<number>): ColorScale {
|
|
336
|
-
return alphas.map((a) => fromOKLCH(L, C, H, a)) as {length: 13; [i: number]: string} as ColorScale;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
export function generateWhiteScale(hue: number, chroma: number, luminosity = 1, chromaMul = 1): ColorScale {
|
|
334
|
+
export function generateWhites(hue: number, chroma: number, luminosity = 1, chromaMul = 1) {
|
|
340
335
|
const c = chroma * 0.1 * chromaMul;
|
|
341
|
-
|
|
336
|
+
const l = (1 - chroma * 0.1) * luminosity;
|
|
337
|
+
return {
|
|
338
|
+
white: fromOKLCH(l, c, hue, 1),
|
|
339
|
+
whiteA: [
|
|
340
|
+
null,
|
|
341
|
+
...[0, 0.01, 0.03, 0.06, 0.09, 0.12, 0.18, 0.25, 0.39, 0.45, 0.59, 0.92].map((a) => fromOKLCH(l, c, hue, a)),
|
|
342
|
+
] as unknown as ColorScale,
|
|
343
|
+
};
|
|
342
344
|
}
|
|
343
345
|
|
|
344
|
-
export function
|
|
346
|
+
export function generateBlacks(hue: number, chroma: number, luminosity = 1, chromaMul = 1) {
|
|
345
347
|
const c = chroma * 0.1 * chromaMul;
|
|
346
|
-
|
|
348
|
+
const l = chroma * 0.1 * 4 * luminosity;
|
|
349
|
+
return {
|
|
350
|
+
black: fromOKLCH(l, c, hue, 1),
|
|
351
|
+
blackA: [
|
|
352
|
+
null,
|
|
353
|
+
...[0.01, 0.03, 0.05, 0.07, 0.09, 0.11, 0.14, 0.22, 0.44, 0.48, 0.57, 0.91].map((a) => fromOKLCH(l, c, hue, a)),
|
|
354
|
+
] as unknown as ColorScale,
|
|
355
|
+
};
|
|
347
356
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import compact from "lodash/compact";
|
|
2
|
+
import isFunction from "lodash/isFunction";
|
|
3
|
+
import isObject from "lodash/isObject";
|
|
2
4
|
import {
|
|
3
5
|
Children,
|
|
4
6
|
ComponentType,
|
|
@@ -37,14 +39,14 @@ const flattenGroupChildren: (
|
|
|
37
39
|
children: ReactNode,
|
|
38
40
|
renderGroup: (Component: ComponentType<GroupHeadingProps>, props: GroupProps["headingProps"]) => ReactNode
|
|
39
41
|
) => ReactNode[] = (children, renderGroup) => {
|
|
40
|
-
return
|
|
42
|
+
return compact(
|
|
41
43
|
Children.toArray(children)
|
|
42
44
|
.map((child) => {
|
|
43
45
|
if (isObject(child) && "props" in child && isObject(child.props)) {
|
|
44
46
|
const {props} = child;
|
|
45
47
|
if (isGroupChild(props)) {
|
|
46
48
|
//well, it's a group
|
|
47
|
-
const children =
|
|
49
|
+
const children = compact(Children.toArray(props.children));
|
|
48
50
|
if (!children.length) {
|
|
49
51
|
// do not render empty groups
|
|
50
52
|
return [];
|
package/src/select/index.tsx
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
/* eslint-disable no-use-before-define */
|
|
2
1
|
import {useIsPhone} from "../use-is-phone";
|
|
3
|
-
import {
|
|
2
|
+
import type {GroupBase, SelectInstance, SelectProps} from "./select";
|
|
3
|
+
import {combineStyles, SelectComponent} from "./select";
|
|
4
4
|
import React, {ForwardedRef, forwardRef} from "react";
|
|
5
|
-
import type {SelectProps, GroupBase, SelectInstance} from "./select";
|
|
6
5
|
import {SelectInPopover} from "./select-in-popover";
|
|
7
|
-
import {combineStyles} from "./select";
|
|
8
6
|
import {makeSingleLineStyles} from "./styles";
|
|
9
7
|
import {css} from "@linaria/core";
|
|
10
8
|
import {$TSFixMe} from "../tsfixme";
|
|
@@ -52,12 +50,13 @@ export const Select = forwardRef(function SelectInner<
|
|
|
52
50
|
placeholder,
|
|
53
51
|
title,
|
|
54
52
|
isSearchable,
|
|
53
|
+
inline,
|
|
55
54
|
...restProps
|
|
56
55
|
}: SelectProps<T, U, V>,
|
|
57
56
|
forwardedRef: ForwardedRef<SelectInstance<T, U, V>>
|
|
58
57
|
) {
|
|
59
58
|
const isPhone = useIsPhone();
|
|
60
|
-
if (isPhone) {
|
|
59
|
+
if (isPhone && !inline) {
|
|
61
60
|
return (
|
|
62
61
|
<SelectInPopover
|
|
63
62
|
{...(restProps as $TSFixMe)}
|
|
@@ -106,6 +105,7 @@ export const Select = forwardRef(function SelectInner<
|
|
|
106
105
|
</div>
|
|
107
106
|
);
|
|
108
107
|
}}
|
|
108
|
+
inline={inline}
|
|
109
109
|
/>
|
|
110
110
|
);
|
|
111
111
|
}
|
|
@@ -119,6 +119,7 @@ export const Select = forwardRef(function SelectInner<
|
|
|
119
119
|
isOptionDisabled={isOptionDisabled}
|
|
120
120
|
isClearable={isClearable}
|
|
121
121
|
styles={styles}
|
|
122
|
+
inline={inline}
|
|
122
123
|
{...restProps}
|
|
123
124
|
/>
|
|
124
125
|
);
|