@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.
- package/CHANGELOG.md +14 -0
- package/eslint.config.mjs +16 -0
- package/package.json +11 -13
- package/src/a11y-color.test.ts +162 -0
- package/src/a11y-color.ts +13 -20
- package/src/actions-menu/actions-menu-item.tsx +9 -12
- 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/ant-modal.tsx +10 -5
- package/src/antd/index.tsx +3 -0
- package/src/antd/input.tsx +0 -1
- package/src/antd/styles.ts +15 -15
- package/src/app-icon-with-fallback.tsx +6 -6
- package/src/app-icon-wrapper.tsx +2 -3
- package/src/app-icon.tsx +4 -4
- package/src/avatar.tsx +3 -3
- package/src/button/base-button.tsx +0 -1
- package/src/button/button.tsx +1 -0
- package/src/button/make-button-colors.ts +5 -13
- package/src/checkbox.tsx +2 -2
- package/src/collapsible-section.tsx +11 -1
- package/src/color-picker/ColorPickerOrLoader.js +2 -2
- package/src/color-utils.test.ts +307 -0
- package/src/color-utils.ts +215 -0
- package/src/command-menu/index.tsx +1 -0
- package/src/comment.tsx +3 -2
- package/src/context-menu/index.tsx +12 -7
- 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 +759 -0
- 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 +45 -22
- package/src/design-system.ts +25 -1339
- package/src/dropdown-menu/index.tsx +21 -16
- package/src/emoji-picker/app-icon-picker.tsx +5 -5
- package/src/emoji-picker/emoji-picker-content-with-color.tsx +4 -4
- package/src/emoji-picker/icon-emoji-picker.tsx +3 -3
- package/src/favorites-icon.tsx +1 -1
- package/src/fibermoji-placeholder.tsx +3 -3
- package/src/field-container.tsx +11 -2
- package/src/file-item/file-icon.tsx +169 -0
- package/src/file-item/file-menu-items.tsx +68 -0
- package/src/file-item/file-preview-actions.tsx +38 -0
- package/src/file-item/file-title.tsx +48 -0
- package/src/file-item/types.ts +27 -0
- package/src/file-item/use-register-in-image-gallery.tsx +70 -0
- package/src/file-item-2.tsx +32 -348
- package/src/file-item.tsx +6 -3
- package/src/hue-shift.test.ts +91 -0
- 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/FileCounter.ts +8 -0
- package/src/icons/ast/FileMultiple.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/ValueEdit.ts +8 -0
- package/src/icons/ast/index.tsx +9 -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/FileCounter.tsx +13 -0
- package/src/icons/react/FileMultiple.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/ValueEdit.tsx +13 -0
- package/src/icons/react/index.tsx +9 -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-counter.svg +3 -0
- package/src/icons/svg/file-multiple.svg +3 -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/icons/svg/value-edit.svg +3 -0
- package/src/images-gallery/images-gallery.tsx +8 -6
- package/src/images-gallery/slide-buttons.tsx +4 -11
- package/src/is-iOS.ts +0 -1
- package/src/is-in-popup.ts +2 -1
- package/src/lists/actions-menu-row-surface.tsx +7 -7
- package/src/loading-sausage.tsx +2 -2
- package/src/media-query-utils.ts +1 -2
- package/src/mobile-keyboard-aware-popup.tsx +6 -5
- 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 +309 -0
- package/src/palette-generator.ts +160 -0
- 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/platform.ts +0 -3
- package/src/popover/get-element-ref.ts +28 -0
- package/src/popover/index.tsx +236 -339
- 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/progress.tsx +2 -2
- package/src/reactions/reaction-button.tsx +12 -6
- package/src/root-theme-provider.test.tsx +411 -0
- package/src/scale-generator.ts +356 -0
- package/src/select/components/menu-list-virtualized.tsx +12 -25
- package/src/select/components/menu.tsx +12 -2
- package/src/select/index.tsx +6 -5
- package/src/select/select.tsx +38 -39
- package/src/select/styles.ts +0 -1
- package/src/select/util.ts +1 -1
- package/src/static-palettes.ts +356 -0
- package/src/thematic-color-picker.tsx +266 -0
- package/src/thematic-constants.tsx +27 -0
- package/src/thematic-controls.tsx +144 -0
- package/src/thematic-scales.tsx +122 -0
- package/src/thematic-state.ts +370 -0
- package/src/thematic.tsx +386 -0
- package/src/theme-provider.test.tsx +820 -0
- package/src/theme-provider.tsx +158 -92
- package/src/theme-settings.ts +67 -12
- package/src/theme-styles.ts +58 -6
- package/src/toast/toast-action.tsx +1 -1
- package/src/toast/toast.tsx +1 -2
- package/src/toggle-on-off.tsx +2 -2
- package/src/toggle.tsx +7 -8
- package/src/tooltip.tsx +14 -10
- package/src/type-badge.tsx +7 -14
- package/src/unit/styles.ts +2 -25
- package/src/unit/unit-with-tooltip.tsx +3 -2
- package/src/use-is-phone.tsx +7 -2
- package/src/use-long-press.tsx +2 -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/create-inline-theme.ts +0 -66
- package/src/number-input/index.js +0 -191
- package/src/palette.ts +0 -237
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {css} from "@linaria/core";
|
|
2
|
-
import
|
|
3
|
-
import {FC,
|
|
2
|
+
import fromPairs from "lodash/fromPairs";
|
|
3
|
+
import {FC, lazy, Suspense} from "react";
|
|
4
4
|
import {a11yColor} from "./a11y-color";
|
|
5
5
|
import {AppIconWrapper} from "./app-icon-wrapper";
|
|
6
|
-
import {
|
|
6
|
+
import {getOpacities, iconSize, layout} from "./design-system";
|
|
7
7
|
import {Icon} from "./icons/Icon";
|
|
8
8
|
import {useTheme} from "./theme-provider";
|
|
9
9
|
import {useIsPhone} from "./use-is-phone";
|
|
@@ -89,7 +89,7 @@ type UglyExternalIconProps = Omit<UglyExternalIconImplProps, "appIconsLookup">;
|
|
|
89
89
|
|
|
90
90
|
const UglyExternalIcon = lazy(async () => {
|
|
91
91
|
const appIcons = await import("./appIcons.json");
|
|
92
|
-
const appIconsLookup =
|
|
92
|
+
const appIconsLookup = fromPairs(
|
|
93
93
|
appIcons.default.flatMap((appIcon) => appIcon.short_names.map((shortName) => [shortName, appIcon]))
|
|
94
94
|
);
|
|
95
95
|
|
|
@@ -128,13 +128,13 @@ export const AppIcon: FC<AppIconProps> = ({icon, color, isSelected, ...rest}) =>
|
|
|
128
128
|
const isPhone = useIsPhone();
|
|
129
129
|
const defaultIconSize = isPhone ? layout.mobileSidebarIconSize : layout.newMenuIconSize;
|
|
130
130
|
const {iconSize = defaultIconSize, containerSize = defaultIconSize} = rest;
|
|
131
|
-
const iconColor =
|
|
131
|
+
const iconColor = theme.select({
|
|
132
132
|
dark: a11yColor(getOpacities(color).opacity95, theme.menuBg, 5),
|
|
133
133
|
light: a11yColor(getOpacities(color).opacity95, theme.whiteColor, 3),
|
|
134
134
|
light2: a11yColor(getOpacities(color).opacity95, theme.menuBg, 5),
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
-
const bgColor =
|
|
137
|
+
const bgColor = theme.fns.getAppIconBackground(color);
|
|
138
138
|
|
|
139
139
|
return (
|
|
140
140
|
<AppIconWrapper color={bgColor as string} borderless={isSelected} containerSize={containerSize}>
|
package/src/app-icon-wrapper.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {css} from "@linaria/core";
|
|
2
2
|
import cn from "classnames";
|
|
3
3
|
import {ReactNode} from "react";
|
|
4
|
-
import {border,
|
|
4
|
+
import {border, layout} from "./design-system";
|
|
5
5
|
import {useTheme} from "./theme-provider";
|
|
6
6
|
import {useIsPhone} from "./use-is-phone";
|
|
7
7
|
|
|
@@ -34,8 +34,7 @@ export const AppIconWrapper = ({
|
|
|
34
34
|
style={{backgroundColor: borderless ? "transparent" : color, width: containerSize, height: containerSize}}
|
|
35
35
|
className={cn(
|
|
36
36
|
appIconContainerStyle,
|
|
37
|
-
!borderless &&
|
|
38
|
-
getThemeValue(theme.mode, {light2: appIconGradientStyle, dark: appIconGradientStyle, light: null})
|
|
37
|
+
!borderless && theme.select({light2: appIconGradientStyle, dark: appIconGradientStyle, light: null})
|
|
39
38
|
)}
|
|
40
39
|
>
|
|
41
40
|
{children}
|
package/src/app-icon.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import cx from "classnames";
|
|
|
3
3
|
import {FC} from "react";
|
|
4
4
|
import {a11yColor} from "./a11y-color";
|
|
5
5
|
import {AppIconWithFallback} from "./app-icon-with-fallback";
|
|
6
|
-
import {border,
|
|
6
|
+
import {border, getOpacities} from "./design-system";
|
|
7
7
|
import {useTheme} from "./theme-provider";
|
|
8
8
|
|
|
9
9
|
interface AppIconProps {
|
|
@@ -16,15 +16,15 @@ interface AppIconProps {
|
|
|
16
16
|
|
|
17
17
|
export const AppIcon: FC<AppIconProps> = ({color, icon, containerSize = 54, iconSize = 36, className}) => {
|
|
18
18
|
const theme = useTheme();
|
|
19
|
-
const iconColor =
|
|
19
|
+
const iconColor = theme.select({
|
|
20
20
|
dark: a11yColor(getOpacities(color).opacity95, theme.menuBg, 5),
|
|
21
21
|
light: a11yColor(getOpacities(color).opacity95, theme.whiteColor, 3),
|
|
22
22
|
light2: a11yColor(getOpacities(color).opacity95, theme.menuBg, 5),
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
const bgColor =
|
|
25
|
+
const bgColor = theme.fns.getAppIconBackground(color);
|
|
26
26
|
|
|
27
|
-
const appIconGradientStyle =
|
|
27
|
+
const appIconGradientStyle = theme.select({
|
|
28
28
|
dark: "linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0))",
|
|
29
29
|
light: "none",
|
|
30
30
|
light2: "linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0))",
|
package/src/avatar.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {css} from "@linaria/core";
|
|
2
|
-
import
|
|
2
|
+
import memoize from "lodash/memoize";
|
|
3
3
|
import {CSSProperties, FC, memo, useCallback, useMemo, useState} from "react";
|
|
4
4
|
import {getDarkenColor, getObjectColorMemoized, textStyles, themeVars} from "./design-system";
|
|
5
5
|
import {FibermojiPlaceholder} from "./fibermoji-placeholder";
|
|
@@ -7,7 +7,7 @@ import {getShiftStyle} from "./icons/get-shift-style";
|
|
|
7
7
|
import {useFeatures} from "./features";
|
|
8
8
|
import {abbreviateName} from "./abbreviate-name";
|
|
9
9
|
|
|
10
|
-
export const getContainerStyle =
|
|
10
|
+
export const getContainerStyle = memoize((containerSize) => {
|
|
11
11
|
return {
|
|
12
12
|
width: containerSize,
|
|
13
13
|
height: containerSize,
|
|
@@ -22,7 +22,7 @@ const statusIconPositionByIndex = [
|
|
|
22
22
|
{top: 0, left: 0},
|
|
23
23
|
];
|
|
24
24
|
|
|
25
|
-
const getStatusIconStyle =
|
|
25
|
+
const getStatusIconStyle = memoize(
|
|
26
26
|
(size, index): CSSProperties => {
|
|
27
27
|
const iconSize = Math.ceil(size / 2);
|
|
28
28
|
const position = statusIconPositionByIndex[index];
|
|
@@ -139,7 +139,6 @@ export const BaseButton = forwardRef<HTMLButtonElement, BaseButtonProps>(
|
|
|
139
139
|
data-variant={variant}
|
|
140
140
|
className={cx(reset, baseButton, className)}
|
|
141
141
|
ref={ref}
|
|
142
|
-
// eslint-disable-next-line react/button-has-type
|
|
143
142
|
type={type}
|
|
144
143
|
{...buttonProps}
|
|
145
144
|
>
|
package/src/button/button.tsx
CHANGED
|
@@ -3,18 +3,10 @@
|
|
|
3
3
|
// so it's easier to find buttons with custom colors
|
|
4
4
|
|
|
5
5
|
import {CSSProperties} from "@linaria/core";
|
|
6
|
-
import {
|
|
7
|
-
getDarkenColor,
|
|
8
|
-
getEnumBackgroundColor,
|
|
9
|
-
getIconColor,
|
|
10
|
-
getOpacities,
|
|
11
|
-
getThemeValue,
|
|
12
|
-
themeVars,
|
|
13
|
-
} from "../design-system";
|
|
6
|
+
import {getDarkenColor, getOpacities, ThemeColors, themeVars} from "../design-system";
|
|
14
7
|
import {ButtonVariant} from "./base-button";
|
|
15
|
-
import {ThemeMode} from "../theme-settings";
|
|
16
8
|
|
|
17
|
-
export const makeButtonColors = (color: string, variant: ButtonVariant,
|
|
9
|
+
export const makeButtonColors = (color: string, variant: ButtonVariant, theme: ThemeColors): CSSProperties => {
|
|
18
10
|
switch (variant) {
|
|
19
11
|
case "solid":
|
|
20
12
|
return {
|
|
@@ -43,12 +35,12 @@ export const makeButtonColors = (color: string, variant: ButtonVariant, mode: Th
|
|
|
43
35
|
};
|
|
44
36
|
default:
|
|
45
37
|
case "soft": {
|
|
46
|
-
const bgColor =
|
|
47
|
-
const textColor = getIconColor(
|
|
38
|
+
const bgColor = theme.fns.getEnumBackground(color) as string;
|
|
39
|
+
const textColor = theme.fns.getIconColor(color) as string;
|
|
48
40
|
|
|
49
41
|
return {
|
|
50
42
|
"--fibery-button-color": bgColor,
|
|
51
|
-
"--fibery-button-hover-color":
|
|
43
|
+
"--fibery-button-hover-color": theme.select({
|
|
52
44
|
light: getOpacities(color).opacity30,
|
|
53
45
|
dark: getOpacities(color).opacity45,
|
|
54
46
|
}),
|
package/src/checkbox.tsx
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
useEffect,
|
|
13
13
|
useRef,
|
|
14
14
|
} from "react";
|
|
15
|
-
import {
|
|
15
|
+
import {getDarkenColor, getOpacities, layout, space, textStyles, ThemeColors, themeVars} from "./design-system";
|
|
16
16
|
import {smallHtmlStylesClass} from "./html-styles";
|
|
17
17
|
import {mobileRootSelector} from "./mobile-styles";
|
|
18
18
|
|
|
@@ -127,7 +127,7 @@ const checkmarkStyle = css`
|
|
|
127
127
|
opacity: 0;
|
|
128
128
|
transition: transform 0.2s, opacity 0.2s;
|
|
129
129
|
pointer-events: none;
|
|
130
|
-
color: ${
|
|
130
|
+
color: ${themeVars.whiteColor};
|
|
131
131
|
width: ${layout.checkboxSize}px;
|
|
132
132
|
height: ${layout.checkboxSize}px;
|
|
133
133
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {css, cx} from "@linaria/core";
|
|
2
2
|
import ArrowBottom from "./icons/react/ArrowBottom";
|
|
3
3
|
import ArrowTop from "./icons/react/ArrowTop";
|
|
4
|
-
import {ReactNode, useCallback} from "react";
|
|
4
|
+
import {ReactNode, useCallback, useEffect, useRef} from "react";
|
|
5
5
|
import {useControllableState} from "@fibery/react/src/use-controllable-state";
|
|
6
6
|
import {space} from "./design-system";
|
|
7
7
|
|
|
@@ -48,6 +48,16 @@ export const CollapsibleSection = ({
|
|
|
48
48
|
|
|
49
49
|
const onToggle = useCallback(() => setIsCollapsed(!isCollapsed), [isCollapsed, setIsCollapsed]);
|
|
50
50
|
|
|
51
|
+
// Ugly one-time auto-expand logic, in case of collapsedDefault is changed from false to true shortly after render
|
|
52
|
+
// (i.e. when something is loaded asynchronously)
|
|
53
|
+
const expandedOnce = useRef(false);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (isCollapsed && !collapsedDefault && !expandedOnce.current) {
|
|
56
|
+
expandedOnce.current = true;
|
|
57
|
+
setIsCollapsed(false);
|
|
58
|
+
}
|
|
59
|
+
}, [isCollapsed, collapsedDefault, setIsCollapsed]);
|
|
60
|
+
|
|
51
61
|
const collapsibleHeader = (
|
|
52
62
|
<div
|
|
53
63
|
tabIndex={0}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {colors, border, space} from "../design-system";
|
|
2
1
|
import {css} from "@linaria/core";
|
|
3
2
|
import {lazy, Suspense} from "react";
|
|
3
|
+
import {border, space, themeVars} from "../design-system";
|
|
4
4
|
|
|
5
5
|
const colorPickerLoaderStyle = css`
|
|
6
6
|
${{
|
|
7
|
-
backgroundColor:
|
|
7
|
+
backgroundColor: themeVars.inversedTextColor,
|
|
8
8
|
borderRadius: border.radius4,
|
|
9
9
|
boxShadow: "rgba(0, 0, 0, 0.15) 0px 0px 0px 1px, rgba(0, 0, 0, 0.15) 0px 8px 16px",
|
|
10
10
|
padding: space.s12,
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fromHSL,
|
|
3
|
+
fromOKLCH,
|
|
4
|
+
getAlpha,
|
|
5
|
+
getColorDistance,
|
|
6
|
+
getContrastRatio,
|
|
7
|
+
getHsl,
|
|
8
|
+
getLuminance,
|
|
9
|
+
isValidColor,
|
|
10
|
+
toCss,
|
|
11
|
+
toOKLCH,
|
|
12
|
+
} from "./color-utils";
|
|
13
|
+
|
|
14
|
+
describe("color-utils", () => {
|
|
15
|
+
describe("css", () => {
|
|
16
|
+
it("normalizes hex to rgb", () => {
|
|
17
|
+
expect(toCss("#ff0000")).toMatchInlineSnapshot(`"rgb(255,0,0)"`);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("normalizes named colors to rgb", () => {
|
|
21
|
+
expect(toCss("red")).toMatchInlineSnapshot(`"rgb(255,0,0)"`);
|
|
22
|
+
expect(toCss("blue")).toMatchInlineSnapshot(`"rgb(0,0,255)"`);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("preserves alpha in rgba", () => {
|
|
26
|
+
expect(toCss("rgba(255, 0, 0, 0.5)")).toMatchInlineSnapshot(`"rgba(255,0,0,0.5)"`);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("converts hsl to rgb", () => {
|
|
30
|
+
expect(toCss("hsl(0, 100%, 50%)")).toMatchInlineSnapshot(`"rgb(255,0,0)"`);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("returns fallback for invalid color", () => {
|
|
34
|
+
expect(toCss("invalid")).toMatchInlineSnapshot(`"rgb(73,120,212)"`);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("isValidColor", () => {
|
|
39
|
+
it("returns true for valid hex colors", () => {
|
|
40
|
+
expect(isValidColor("#ff0000")).toBe(true);
|
|
41
|
+
expect(isValidColor("#f00")).toBe(true);
|
|
42
|
+
expect(isValidColor("#FF0000")).toBe(true);
|
|
43
|
+
expect(isValidColor("#ff000080")).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns true for valid rgb/rgba colors", () => {
|
|
47
|
+
expect(isValidColor("rgb(255, 0, 0)")).toBe(true);
|
|
48
|
+
expect(isValidColor("rgba(255, 0, 0, 0.5)")).toBe(true);
|
|
49
|
+
expect(isValidColor("rgb(255,0,0)")).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("returns true for valid hsl/hsla colors", () => {
|
|
53
|
+
expect(isValidColor("hsl(0, 100%, 50%)")).toBe(true);
|
|
54
|
+
expect(isValidColor("hsla(0, 100%, 50%, 0.5)")).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("returns true for named colors", () => {
|
|
58
|
+
expect(isValidColor("red")).toBe(true);
|
|
59
|
+
expect(isValidColor("blue")).toBe(true);
|
|
60
|
+
expect(isValidColor("white")).toBe(true);
|
|
61
|
+
expect(isValidColor("black")).toBe(true);
|
|
62
|
+
expect(isValidColor("cornflowerblue")).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("returns true for transparent", () => {
|
|
66
|
+
expect(isValidColor("transparent")).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("returns false for invalid colors", () => {
|
|
70
|
+
expect(isValidColor("invalid")).toBe(false);
|
|
71
|
+
expect(isValidColor("not-a-color")).toBe(false);
|
|
72
|
+
expect(isValidColor("")).toBe(false);
|
|
73
|
+
expect(isValidColor("#gggggg")).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("getAlpha", () => {
|
|
78
|
+
it("returns 1 for solid colors", () => {
|
|
79
|
+
expect(getAlpha("#ff0000")).toBe(1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("returns 1 for solid rgb colors", () => {
|
|
83
|
+
expect(getAlpha("rgb(255, 0, 0)")).toBe(1);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("returns correct alpha for rgba", () => {
|
|
87
|
+
expect(getAlpha("rgba(255, 0, 0, 0.5)")).toBeCloseTo(0.5, 2);
|
|
88
|
+
expect(getAlpha("rgba(255, 0, 0, 0)")).toBe(0);
|
|
89
|
+
expect(getAlpha("rgba(255, 0, 0, 1)")).toBe(1);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("returns correct alpha for hsla colors", () => {
|
|
93
|
+
expect(getAlpha("hsla(0, 100%, 50%, 0.7)")).toBeCloseTo(0.7, 2);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("toCss", () => {
|
|
98
|
+
it("converts to rgb for opaque colors", () => {
|
|
99
|
+
const result = toCss("#ff0000");
|
|
100
|
+
expect(result).toMatchInlineSnapshot(`"rgb(255,0,0)"`);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("converts to rgba for colors with alpha", () => {
|
|
104
|
+
const result = toCss("rgba(255, 0, 0, 0.5)");
|
|
105
|
+
expect(result).toMatchInlineSnapshot(`"rgba(255,0,0,0.5)"`);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("getContrastRatio", () => {
|
|
110
|
+
it("returns 21 for black on white", () => {
|
|
111
|
+
expect(getContrastRatio("#000000", "#ffffff")).toBeCloseTo(21, 0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("returns 21 for white on black", () => {
|
|
115
|
+
expect(getContrastRatio("#ffffff", "#000000")).toBeCloseTo(21, 0);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("returns 1 for same colors", () => {
|
|
119
|
+
expect(getContrastRatio("#808080", "#808080")).toBeCloseTo(1, 0);
|
|
120
|
+
expect(getContrastRatio("#ff0000", "#ff0000")).toBeCloseTo(1, 0);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("returns symmetric results", () => {
|
|
124
|
+
const ratio1 = getContrastRatio("#ff0000", "#00ff00");
|
|
125
|
+
const ratio2 = getContrastRatio("#00ff00", "#ff0000");
|
|
126
|
+
expect(ratio1).toBeCloseTo(ratio2, 5);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("returns expected values for known pairs", () => {
|
|
130
|
+
// Red on white should have moderate contrast
|
|
131
|
+
const redWhite = getContrastRatio("#ff0000", "#ffffff");
|
|
132
|
+
expect(redWhite).toBeGreaterThan(3);
|
|
133
|
+
expect(redWhite).toBeLessThan(5);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("getColorDistance", () => {
|
|
138
|
+
it("returns 0 for same colors", () => {
|
|
139
|
+
expect(getColorDistance("#ff0000", "#ff0000")).toBe(0);
|
|
140
|
+
expect(getColorDistance("red", "red")).toBe(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("returns large value for very different colors", () => {
|
|
144
|
+
// Red and green are perceptually very different
|
|
145
|
+
const distance = getColorDistance("#ff0000", "#00ff00");
|
|
146
|
+
expect(distance).toBeGreaterThan(50);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("returns small value for similar colors", () => {
|
|
150
|
+
const distance = getColorDistance("#ff0000", "#ff1100");
|
|
151
|
+
expect(distance).toBeLessThan(10);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("is symmetric", () => {
|
|
155
|
+
const d1 = getColorDistance("#ff0000", "#0000ff");
|
|
156
|
+
const d2 = getColorDistance("#0000ff", "#ff0000");
|
|
157
|
+
expect(d1).toBeCloseTo(d2, 5);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("getLuminance", () => {
|
|
162
|
+
it("returns 1 for white", () => {
|
|
163
|
+
expect(getLuminance("#ffffff")).toBeCloseTo(1, 2);
|
|
164
|
+
expect(getLuminance("white")).toBeCloseTo(1, 2);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("returns 0 for black", () => {
|
|
168
|
+
expect(getLuminance("#000000")).toBeCloseTo(0, 2);
|
|
169
|
+
expect(getLuminance("black")).toBeCloseTo(0, 2);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("returns ~0.5 for middle gray", () => {
|
|
173
|
+
// Note: luminance is perceptual, not linear
|
|
174
|
+
// Middle gray (#808080) has luminance ~0.216
|
|
175
|
+
expect(getLuminance("#808080")).toBeGreaterThan(0.1);
|
|
176
|
+
expect(getLuminance("#808080")).toBeLessThan(0.3);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("returns values between 0 and 1", () => {
|
|
180
|
+
expect(getLuminance("#ff0000")).toBeGreaterThanOrEqual(0);
|
|
181
|
+
expect(getLuminance("#ff0000")).toBeLessThanOrEqual(1);
|
|
182
|
+
expect(getLuminance("#00ff00")).toBeGreaterThanOrEqual(0);
|
|
183
|
+
expect(getLuminance("#00ff00")).toBeLessThanOrEqual(1);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe("hsl", () => {
|
|
188
|
+
it("creates red at hue 0", () => {
|
|
189
|
+
expect(fromHSL(0, 1, 0.5)).toMatchInlineSnapshot(`"rgb(255,0,0)"`);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("creates green at hue 120", () => {
|
|
193
|
+
const color = fromHSL(120, 1, 0.5);
|
|
194
|
+
expect(color).toMatchInlineSnapshot(`"rgb(0,255,0)"`);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("creates blue at hue 240", () => {
|
|
198
|
+
const color = fromHSL(240, 1, 0.5);
|
|
199
|
+
expect(color).toMatchInlineSnapshot(`"rgb(0,0,255)"`);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("creates white at lightness 1", () => {
|
|
203
|
+
const color = fromHSL(0, 1, 1);
|
|
204
|
+
expect(color).toMatchInlineSnapshot(`"rgb(255,255,255)"`);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("creates black at lightness 0", () => {
|
|
208
|
+
const color = fromHSL(0, 1, 0);
|
|
209
|
+
expect(color).toMatchInlineSnapshot(`"rgb(0,0,0)"`);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("creates gray at saturation 0", () => {
|
|
213
|
+
const color = fromHSL(0, 0, 0.5);
|
|
214
|
+
expect(color).toMatchInlineSnapshot(`"rgb(128,128,128)"`);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe("fromLCH (OKLCH)", () => {
|
|
219
|
+
it("creates color from OKLCH values", () => {
|
|
220
|
+
expect(fromOKLCH(0.592, 0.16, 264)).toMatchInlineSnapshot(`"rgb(76,120,220)"`);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("creates white at L=1 with zero chroma", () => {
|
|
224
|
+
expect(fromOKLCH(1, 0, 0)).toMatchInlineSnapshot(`"rgb(255,255,255)"`);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("creates black at L=0", () => {
|
|
228
|
+
expect(fromOKLCH(0, 0, 0)).toMatchInlineSnapshot(`"rgb(0,0,0)"`);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("creates gray at mid-lightness with zero chroma", () => {
|
|
232
|
+
expect(fromOKLCH(0.5, 0, 0)).toMatchInlineSnapshot(`"rgb(99,99,99)"`);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("creates saturated red at hue ~29", () => {
|
|
236
|
+
expect(fromOKLCH(0.628, 0.258, 29)).toMatchInlineSnapshot(`"rgb(255,0,3)"`);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("creates saturated blue at hue ~264", () => {
|
|
240
|
+
expect(fromOKLCH(0.452, 0.313, 264)).toMatchInlineSnapshot(`"rgb(0,1,255)"`);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("higher chroma produces more saturated colors", () => {
|
|
244
|
+
expect(fromOKLCH(0.6, 0.05, 264)).toMatchInlineSnapshot(`"rgb(113,128,159)"`);
|
|
245
|
+
expect(fromOKLCH(0.6, 0.2, 264)).toMatchInlineSnapshot(`"rgb(64,118,245)"`);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("gamut maps out-of-range colors", () => {
|
|
249
|
+
expect(fromOKLCH(0.7, 0.4, 150)).toMatchInlineSnapshot(`"rgb(0,194,72)"`);
|
|
250
|
+
expect(fromOKLCH(0.8, 0.4, 150)).toMatchInlineSnapshot(`"rgb(0,231,90)"`);
|
|
251
|
+
expect(fromOKLCH(0.9, 0.4, 150)).toMatchInlineSnapshot(`"rgb(65,255,135)"`);
|
|
252
|
+
expect(fromOKLCH(1, 0.4, 150)).toMatchInlineSnapshot(`"rgb(255,255,255)"`);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe("getHsl", () => {
|
|
257
|
+
it("extracts HSL from red", () => {
|
|
258
|
+
const [h, s, l] = getHsl("#ff0000");
|
|
259
|
+
expect(h).toBeCloseTo(0, 0);
|
|
260
|
+
expect(s).toBeCloseTo(1, 2);
|
|
261
|
+
expect(l).toBeCloseTo(0.5, 2);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("extracts HSL from green", () => {
|
|
265
|
+
const [h, s, l] = getHsl("#00ff00");
|
|
266
|
+
expect(h).toBeCloseTo(120, 0);
|
|
267
|
+
expect(s).toBeCloseTo(1, 2);
|
|
268
|
+
expect(l).toBeCloseTo(0.5, 2);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("extracts HSL from blue", () => {
|
|
272
|
+
const [h, s, l] = getHsl("#0000ff");
|
|
273
|
+
expect(h).toBeCloseTo(240, 0);
|
|
274
|
+
expect(s).toBeCloseTo(1, 2);
|
|
275
|
+
expect(l).toBeCloseTo(0.5, 2);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("extracts HSL from white", () => {
|
|
279
|
+
const [, , l] = getHsl("#ffffff");
|
|
280
|
+
expect(l).toBeCloseTo(1, 2);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("extracts HSL from black", () => {
|
|
284
|
+
const [, l] = getHsl("#000000");
|
|
285
|
+
expect(l).toBeCloseTo(0, 2);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("returns 0 for undefined hue (grayscale)", () => {
|
|
289
|
+
const [h] = getHsl("#808080");
|
|
290
|
+
expect(h).toBe(0); // NaN converted to 0
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe("produceColor", () => {
|
|
295
|
+
it("outputs oklch format when OKLCH space is specified", () => {
|
|
296
|
+
expect(toOKLCH("#ff0000")).toMatchInlineSnapshot(`"oklch(62.796% 0.25768 29.234)"`);
|
|
297
|
+
expect(toOKLCH("rgb(0, 128, 255)")).toMatchInlineSnapshot(`"oklch(61.517% 0.21082 256.1)"`);
|
|
298
|
+
expect(toOKLCH("oklch(61.517% 0.21082 256.1)")).toMatchInlineSnapshot(`"oklch(61.517% 0.21082 256.1)"`);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("parses display-p3 colors correctly (P3 space removed for bundle size)", () => {
|
|
302
|
+
expect(toOKLCH("color(display-p3 0.992 0.992 0.978)")).toMatchInlineSnapshot(`"oklch(99.285% 0.00516 106.49)"`);
|
|
303
|
+
expect(toOKLCH("color(display-p3 0.995 0.99 0.922)")).toMatchInlineSnapshot(`"oklch(98.841% 0.02505 102.92)"`);
|
|
304
|
+
expect(toOKLCH("color(display-p3 0.997 0.982 0.749)")).toMatchInlineSnapshot(`"oklch(97.402% 0.08449 104.36)"`);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
});
|