cherry-styled-components 0.2.19 → 0.2.21

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/dist/avatar.js CHANGED
@@ -32,9 +32,10 @@ function toInitials(name) {
32
32
  }
33
33
  var StyledAvatar = styled.span.withConfig({
34
34
  displayName: "avatar__StyledAvatar",
35
- componentId: "sc-3c0e28ca-0"
35
+ componentId: "sc-5bf22726-0"
36
36
  })([``, `;`], ({ theme, $size = "default", $color = "primary" }) => {
37
37
  const { box } = avatarSizes[$size];
38
+ const fill = $color === "primary" ? "primary" : "other";
38
39
  return css([
39
40
  `display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;overflow:hidden;width:`,
40
41
  `px;height:`,
@@ -43,7 +44,7 @@ var StyledAvatar = styled.span.withConfig({
43
44
  `;font-size:`,
44
45
  `px;line-height:1;font-weight:600;user-select:none;`,
45
46
  `;& img{width:100%;height:100%;object-fit:cover;display:block;}`
46
- ], box, box, avatarColors[$color](theme), filledTextColor(theme), Math.round(box * .38), darkFilledTextRule(theme));
47
+ ], box, box, avatarColors[$color](theme), filledTextColor(theme, fill), Math.round(box * .38), darkFilledTextRule(theme, fill));
47
48
  });
48
49
  function LocalAvatar({ $size = "default", $src, $alt, $name, $icon = User, $color, ...props }, ref) {
49
50
  const initials = $name ? toInitials($name) : "";
package/dist/button.js CHANGED
@@ -29,7 +29,7 @@ var buttonStyles = (theme, $variant, $size, $outline, $fullWidth, $error, disabl
29
29
  `;}&:focus{box-shadow:0 0 0 4px `,
30
30
  `;}&:active{box-shadow:0 0 0 2px `,
31
31
  `;}`
32
- ], $outline ? theme.colors.primary : filledTextColor(theme), !$outline && darkFilledTextRule(theme), $outline ? "transparent" : theme.colors.primary, theme.colors.primary, theme.colors.primary, $outline ? "transparent" : theme.colors.primaryDark, theme.colors.primaryDark, $outline && `color: ${theme.colors.primaryDark}`, theme.colors.primaryLight, theme.colors.primaryLight), !disabled && $variant === "secondary" && css([
32
+ ], $outline ? theme.colors.primary : filledTextColor(theme, "primary"), !$outline && darkFilledTextRule(theme, "primary"), $outline ? "transparent" : theme.colors.primary, theme.colors.primary, theme.colors.primary, $outline ? "transparent" : theme.colors.primaryDark, theme.colors.primaryDark, $outline && `color: ${theme.colors.primaryDark}`, theme.colors.primaryLight, theme.colors.primaryLight), !disabled && $variant === "secondary" && css([
33
33
  `color:`,
34
34
  `;`,
35
35
  ` background:`,
@@ -80,7 +80,7 @@ var buttonStyles = (theme, $variant, $size, $outline, $fullWidth, $error, disabl
80
80
  `, $fullWidth && `width: 100%;`);
81
81
  var StyledButton = styled.button.withConfig({
82
82
  displayName: "button__StyledButton",
83
- componentId: "sc-286fdf36-0"
83
+ componentId: "sc-75947fe4-0"
84
84
  })([``, ``], ({ theme, $variant, $error, $size, $outline, $fullWidth, disabled }) => buttonStyles(theme, $variant, $size, $outline, $fullWidth, $error, disabled));
85
85
  function LocalButton({ $variant = "primary", ...props }, ref) {
86
86
  return /*#__PURE__*/ jsxs(StyledButton, {
@@ -19,7 +19,7 @@ import styled, { css } from "styled-components";
19
19
  ], theme.fontSizes.text.xs, theme.lineHeights.text.xs, mq("lg"), theme.fontSizes.small.lg, theme.lineHeights.small.lg);
20
20
  var StyledChatMessage = styled.div.withConfig({
21
21
  displayName: "chat-message__StyledChatMessage",
22
- componentId: "sc-878d3851-0"
22
+ componentId: "sc-3bca3977-0"
23
23
  })([
24
24
  `display:flex;align-items:flex-end;gap:10px;width:100%;& .chat-message-avatar{flex-shrink:0;display:flex;align-items:center;height:calc( `,
25
25
  ` * `,
@@ -36,7 +36,7 @@ var StyledChatMessage = styled.div.withConfig({
36
36
  `;color:`,
37
37
  `;`,
38
38
  `;padding:9px 14px;border-radius:16px 16px 4px 16px;margin-left:auto;width:fit-content;max-width:min(560px,88%);font-weight:500;white-space:pre-wrap;overflow-wrap:anywhere;}`
39
- ], theme.colors.primary, filledTextColor(theme), darkFilledTextRule(theme)) : css([`& .chat-message-content{color:`, `;width:100%;}& .chat-message-avatar{position:sticky;bottom:0;}`], theme.colors.dark));
39
+ ], theme.colors.primary, filledTextColor(theme, "primary"), darkFilledTextRule(theme, "primary")) : css([`& .chat-message-content{color:`, `;width:100%;}& .chat-message-avatar{position:sticky;bottom:0;}`], theme.colors.dark));
40
40
  function LocalChatMessage({ children, $role = "assistant", $avatar, ...props }, ref) {
41
41
  return /*#__PURE__*/ jsxs(StyledChatMessage, {
42
42
  $role,
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ import { Flex } from "./flex.js";
34
34
  import { Grid } from "./grid.js";
35
35
  import { Input, StyledInputWrapper, StyledLabel } from "./input.js";
36
36
  import { MaxWidth } from "./max-width.js";
37
- import { Modal } from "./modal.js";
37
+ import { Modal, useModalLayer } from "./modal.js";
38
38
  import { Password } from "./password.js";
39
39
  import { Range } from "./range.js";
40
40
  import { Select, StyledIconWrapper } from "./select.js";
@@ -44,4 +44,4 @@ import { Textarea } from "./textarea.js";
44
44
  import { ThemeToggle } from "./theme-toggle.js";
45
45
  import { StyledNotificationItem, StyledNotifications, ToastNotifications, ToastNotificationsContext, ToastNotificationsProvider, useToastNotifications } from "./toast.js";
46
46
  import { Toggle } from "./toggle.js";
47
- export { Accordion, Avatar, AvatarDropzone, Box, Button, Callout, ChatContext, ChatInput, ChatLauncher, ChatMessage, ChatMessageList, ChatPanel, ChatProvider, ChatSource, ChatSources, ChatTyping, CherryThemeProvider, ClientThemeProvider, Col, Container, Dropzone, Flex, GlobalStyles, Grid, Icon, IconButton, Input, MaxWidth, Modal, Password, Prose, Range, Select, Space, Spinner, StyledIconWrapper, StyledInputWrapper, StyledLabel, StyledNotificationItem, StyledNotifications, TabContent, Tabs, Textarea, ThemeContext, ThemeToggle, ToastNotifications, ToastNotificationsContext, ToastNotificationsProvider, Toggle, alpha, breakpoints, buttonStyles, chatTextStyles, colors, colorsDark, createThemeInitScript, createTypographyStyle, darkFilledTextRule, errorInteractiveStyles, filledTextColor, fontSizes, fonts, formElementHeightStyles, fullWidthStyles, generateAlignContentStyles, generateAlignItemsStyles, generateColSpanStyles, generateColsStyles, generateDirectionStyles, generateGapStyles, generateJustifyContentStyles, generatePaddingStyles, iconButtonStyles, interactiveStyles, lineHeights, matchesAccept, mq, proseStyles, renderIcon, resetButton, resetInput, resolveShowcaseCommand, resolveTheme, shade, shadows, shadowsDark, showcaseCommands, spacing, statusBorderStyles, styledBlockquote, styledButton, styledButtonBig, styledCode, styledH1, styledH2, styledH3, styledH4, styledH5, styledH6, styledHero1, styledHero2, styledHero3, styledInput, styledInputBig, styledSmall, styledStrong, styledText, theme, themeDark, themeInitScript, thinScrollbar, tint, useBelowBreakpoint, useChat, useLockBodyScroll, useMediaQuery, useOnClickOutside, useToastNotifications };
47
+ export { Accordion, Avatar, AvatarDropzone, Box, Button, Callout, ChatContext, ChatInput, ChatLauncher, ChatMessage, ChatMessageList, ChatPanel, ChatProvider, ChatSource, ChatSources, ChatTyping, CherryThemeProvider, ClientThemeProvider, Col, Container, Dropzone, Flex, GlobalStyles, Grid, Icon, IconButton, Input, MaxWidth, Modal, Password, Prose, Range, Select, Space, Spinner, StyledIconWrapper, StyledInputWrapper, StyledLabel, StyledNotificationItem, StyledNotifications, TabContent, Tabs, Textarea, ThemeContext, ThemeToggle, ToastNotifications, ToastNotificationsContext, ToastNotificationsProvider, Toggle, alpha, breakpoints, buttonStyles, chatTextStyles, colors, colorsDark, createThemeInitScript, createTypographyStyle, darkFilledTextRule, errorInteractiveStyles, filledTextColor, fontSizes, fonts, formElementHeightStyles, fullWidthStyles, generateAlignContentStyles, generateAlignItemsStyles, generateColSpanStyles, generateColsStyles, generateDirectionStyles, generateGapStyles, generateJustifyContentStyles, generatePaddingStyles, iconButtonStyles, interactiveStyles, lineHeights, matchesAccept, mq, proseStyles, renderIcon, resetButton, resetInput, resolveShowcaseCommand, resolveTheme, shade, shadows, shadowsDark, showcaseCommands, spacing, statusBorderStyles, styledBlockquote, styledButton, styledButtonBig, styledCode, styledH1, styledH2, styledH3, styledH4, styledH5, styledH6, styledHero1, styledHero2, styledHero3, styledInput, styledInputBig, styledSmall, styledStrong, styledText, theme, themeDark, themeInitScript, thinScrollbar, tint, useBelowBreakpoint, useChat, useLockBodyScroll, useMediaQuery, useModalLayer, useOnClickOutside, useToastNotifications };
package/dist/modal.d.ts CHANGED
@@ -14,6 +14,16 @@ export interface ModalProps {
14
14
  .modal-title, .modal-content. */
15
15
  className?: string;
16
16
  style?: React.CSSProperties;
17
+ /** Joins the modal stack (default true): a Modal or `useModalLayer` cover
18
+ opened over this one hides it until it closes again. Pass false to let
19
+ this modal and the ones over it show at the same time. */
20
+ $stack?: boolean;
17
21
  }
18
- declare function Modal({ children, $isOpen, $onClose, $title, $width, $hideCloseButton, className, style, }: ModalProps): React.ReactPortal | null;
19
- export { Modal };
22
+ /**
23
+ * Puts a full-page layer that is not a Modal (an outage screen, a sign-in
24
+ * wall) on the modal stack while `open`, so an open Modal fades away under
25
+ * it and returns once it is gone. Returns whether another layer covers it.
26
+ */
27
+ declare function useModalLayer(open: boolean): boolean;
28
+ declare function Modal({ children, $isOpen, $onClose, $title, $width, $hideCloseButton, className, style, $stack, }: ModalProps): React.ReactPortal | null;
29
+ export { Modal, useModalLayer };
package/dist/modal.js CHANGED
@@ -7,7 +7,7 @@ import { useOnClickOutside } from "./utils/use-on-click-outside.js";
7
7
  import { Icon } from "./icon.js";
8
8
  import { IconButton } from "./icon-button.js";
9
9
  import { jsx, jsxs } from "react/jsx-runtime";
10
- import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
10
+ import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
11
11
  import styled, { css, keyframes } from "styled-components";
12
12
  import { X } from "lucide-react";
13
13
  import { createPortal } from "react-dom";
@@ -16,31 +16,84 @@ var emptySubscribe = () => () => {};
16
16
  function useIsClient() {
17
17
  return useSyncExternalStore(emptySubscribe, () => true, () => false);
18
18
  }
19
+ var layers = [];
20
+ var layerListeners = /* @__PURE__ */ new Set();
21
+ function subscribeLayers(listener) {
22
+ layerListeners.add(listener);
23
+ return () => {
24
+ layerListeners.delete(listener);
25
+ };
26
+ }
27
+ function setLayers(next) {
28
+ layers = next;
29
+ for (const listener of layerListeners) listener();
30
+ }
31
+ var COVERED = 1;
32
+ var UNDER_COVER = 2;
33
+ var STACKED = 4;
34
+ function layerFlags(id) {
35
+ const index = layers.findIndex((layer) => layer.id === id);
36
+ if (index === -1) return 0;
37
+ let flags = 0;
38
+ if (index < layers.length - 1) flags |= COVERED;
39
+ if (layers.slice(index + 1).some((layer) => layer.kind === "cover")) flags |= UNDER_COVER;
40
+ if (index > 0 && layers[index - 1].kind === "modal") flags |= STACKED;
41
+ return flags;
42
+ }
43
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
44
+ function useLayer(open, kind) {
45
+ const id = useId();
46
+ useIsomorphicLayoutEffect(() => {
47
+ if (!open) return;
48
+ setLayers([...layers, {
49
+ id,
50
+ kind
51
+ }]);
52
+ return () => setLayers(layers.filter((layer) => layer.id !== id));
53
+ }, [
54
+ open,
55
+ id,
56
+ kind
57
+ ]);
58
+ return useSyncExternalStore(subscribeLayers, () => layerFlags(id), () => 0);
59
+ }
60
+ /**
61
+ * Puts a full-page layer that is not a Modal (an outage screen, a sign-in
62
+ * wall) on the modal stack while `open`, so an open Modal fades away under
63
+ * it and returns once it is gone. Returns whether another layer covers it.
64
+ */ function useModalLayer(open) {
65
+ return (useLayer(open, "cover") & COVERED) !== 0;
66
+ }
19
67
  var overlayFadeIn = keyframes([`from{opacity:0;}to{opacity:1;}`]);
20
68
  var overlayFadeOut = keyframes([`from{opacity:1;}to{opacity:0;}`]);
21
69
  var innerRiseIn = keyframes([`from{transform:translateY(40px);}to{transform:translateY(0);}`]);
22
70
  var innerSinkOut = keyframes([`from{transform:translateY(0);}to{transform:translateY(40px);}`]);
71
+ var overlayHide = keyframes([`from{opacity:1;}to{opacity:0;visibility:hidden;}`]);
72
+ var cardOut = keyframes([`from{opacity:1;transform:translateY(0);}to{opacity:0;transform:translateY(40px);visibility:hidden;}`]);
73
+ var cardGone = keyframes([`from{opacity:0;visibility:hidden;}to{opacity:0;visibility:hidden;}`]);
74
+ var cardIn = keyframes([`from{opacity:0;transform:translateY(40px);}to{opacity:1;transform:translateY(0);}`]);
23
75
  var StyledModal = styled.div.withConfig({
24
76
  displayName: "modal__StyledModal",
25
- componentId: "sc-937855ae-0"
77
+ componentId: "sc-44ff4d11-0"
26
78
  })([
27
79
  `position:fixed;top:0;left:0;width:100%;height:100%;background:`,
28
80
  `;display:flex;justify-content:center;align-items:center;z-index:1010;animation:`,
29
- ` 0.3s ease both;`,
81
+ ` 0.3s ease both;transition:background-color 0.3s ease;`,
82
+ ` `,
30
83
  ` & .modal-inner{background:`,
31
84
  `;border-radius:`,
32
85
  `;padding:20px;max-width:calc(100% - 40px);width:100%;margin:auto;position:relative;animation:`,
33
- ` 0.3s ease both;`,
86
+ `;`,
34
87
  `{max-width:500px;`,
35
88
  `}}`
36
- ], ({ theme }) => alpha(theme.colors.primary, 50), ({ $isClosing }) => $isClosing ? overlayFadeOut : overlayFadeIn, ({ $isClosing }) => $isClosing && css([`pointer-events:none;`]), ({ theme }) => theme.colors.light, ({ theme }) => theme.spacing.radius.lg, ({ $isClosing }) => $isClosing ? innerSinkOut : innerRiseIn, mq("lg"), ({ $width }) => $width && css([`max-width:`, `px;`], $width));
89
+ ], ({ theme }) => alpha(theme.colors.primary, 50), ({ $isClosing, $layer }) => $isClosing ? overlayFadeOut : $layer === "under-cover" ? overlayHide : overlayFadeIn, ({ $bare }) => $bare && css([`background:transparent;`]), ({ $isClosing, $layer }) => ($isClosing || $layer === "covered" || $layer === "under-cover") && css([`pointer-events:none;`]), ({ theme }) => theme.colors.light, ({ theme }) => theme.spacing.radius.lg, ({ $isClosing, $layer, $cardAway }) => $isClosing ? css([``, ` 0.3s ease both`], $cardAway ? cardGone : innerSinkOut) : $layer === "covered" || $layer === "under-cover" ? css([``, ` 0.3s ease both`], cardOut) : $layer === "arriving" ? css([``, ` 0.3s ease 0.3s both`], cardIn) : css([``, ` 0.3s ease both`], innerRiseIn), mq("lg"), ({ $width }) => $width && css([`max-width:`, `px;`], $width));
37
90
  var StyledModalClose = styled(IconButton).withConfig({
38
91
  displayName: "modal__StyledModalClose",
39
- componentId: "sc-937855ae-1"
92
+ componentId: "sc-44ff4d11-1"
40
93
  })([`position:absolute;top:20px;right:20px;z-index:10;`]);
41
94
  var StyledModalTitle = styled.h2.withConfig({
42
95
  displayName: "modal__StyledModalTitle",
43
- componentId: "sc-937855ae-2"
96
+ componentId: "sc-44ff4d11-2"
44
97
  })([
45
98
  `--divider-color:`,
46
99
  `;margin:0 0 15px 0;padding:0 0 15px 0;color:`,
@@ -49,22 +102,40 @@ var StyledModalTitle = styled.h2.withConfig({
49
102
  ], ({ theme }) => theme.colors.grayLight, ({ theme }) => theme.colors.dark, ({ theme }) => styledH5(theme));
50
103
  var StyledModalContent = styled.div.withConfig({
51
104
  displayName: "modal__StyledModalContent",
52
- componentId: "sc-937855ae-3"
105
+ componentId: "sc-44ff4d11-3"
53
106
  })([
54
107
  `max-height:calc(100svh - 200px);overflow-y:auto;padding:5px;margin:-5px;--divider-color:`,
55
108
  `;`,
56
109
  `;& hr{margin:20px 0;border:none;border-bottom:solid 1px var(--divider-color);}`
57
110
  ], ({ theme }) => theme.colors.grayLight, ({ theme }) => styledText(theme));
58
- function Modal({ children, $isOpen, $onClose, $title, $width, $hideCloseButton, className, style }) {
111
+ function Modal({ children, $isOpen, $onClose, $title, $width, $hideCloseButton, className, style, $stack = true }) {
59
112
  const wrapperRef = useRef(null);
60
113
  const elmRef = useRef(null);
61
114
  const isClient = useIsClient();
62
115
  const [shouldRender, setShouldRender] = useState($isOpen);
63
- if ($isOpen && !shouldRender) setShouldRender(true);
116
+ const [hasStacked, setHasStacked] = useState(false);
117
+ const [arrived, setArrived] = useState(false);
118
+ if ($isOpen && !shouldRender) {
119
+ setShouldRender(true);
120
+ setHasStacked(false);
121
+ setArrived(false);
122
+ }
123
+ const live = useLayer($isOpen && $stack, "modal");
124
+ const [shown, setShown] = useState(live);
125
+ if ($isOpen && live !== shown) setShown(live);
126
+ const flags = $isOpen ? live : shown;
127
+ const covered = (flags & COVERED) !== 0;
128
+ const underCover = (flags & UNDER_COVER) !== 0;
129
+ const stacked = (flags & STACKED) !== 0;
130
+ if ($isOpen && (covered || stacked) && !hasStacked) setHasStacked(true);
131
+ if ($isOpen && covered && arrived) setArrived(false);
132
+ if (!$isOpen && shouldRender && underCover) setShouldRender(false);
133
+ const layer = underCover ? "under-cover" : covered ? "covered" : hasStacked ? "arriving" : void 0;
134
+ const cardAway = covered || hasStacked && !arrived;
64
135
  const isClosing = !$isOpen && shouldRender;
65
136
  const closeModal = useCallback(() => {
66
- $onClose();
67
- }, [$onClose]);
137
+ if (!covered) $onClose();
138
+ }, [$onClose, covered]);
68
139
  useEffect(() => {
69
140
  if (!$isOpen) return;
70
141
  function handleKeydown(event) {
@@ -84,6 +155,9 @@ function Modal({ children, $isOpen, $onClose, $title, $width, $hideCloseButton,
84
155
  if (!isClient || !shouldRender) return null;
85
156
  return /*#__PURE__*/ createPortal(/*#__PURE__*/ jsx(StyledModal, {
86
157
  $isClosing: isClosing,
158
+ $layer: layer,
159
+ $cardAway: cardAway,
160
+ $bare: stacked,
87
161
  $width,
88
162
  className,
89
163
  style,
@@ -91,6 +165,9 @@ function Modal({ children, $isOpen, $onClose, $title, $width, $hideCloseButton,
91
165
  children: /*#__PURE__*/ jsxs("div", {
92
166
  className: "modal-inner",
93
167
  ref: wrapperRef,
168
+ onAnimationEnd: (event) => {
169
+ if (event.target === event.currentTarget && layer === "arriving") setArrived(true);
170
+ },
94
171
  role: "dialog",
95
172
  "aria-modal": "true",
96
173
  "aria-label": $title,
@@ -118,4 +195,4 @@ function Modal({ children, $isOpen, $onClose, $title, $width, $hideCloseButton,
118
195
  }), document.body);
119
196
  }
120
197
  //#endregion
121
- export { Modal };
198
+ export { Modal, useModalLayer };
@@ -77,7 +77,9 @@ function resolveColor(value) {
77
77
  }, [effectiveTheme.isDark, reconciled]);
78
78
  useEffect(() => {
79
79
  if (!$themeColor) return;
80
- const background = resolveColor(effectiveTheme.colors[$themeColor]);
80
+ const color = effectiveTheme.colors[$themeColor];
81
+ if (!color) return;
82
+ const background = resolveColor(color);
81
83
  let meta = document.querySelector("meta[name=\"theme-color\"]");
82
84
  if (!meta) {
83
85
  meta = document.createElement("meta");
@@ -9,18 +9,23 @@ export declare const alpha: (color: string, percent: number) => string;
9
9
  export declare const shade: (color: string, percent: number) => string;
10
10
  /** Lightens a color by mixing it toward white. See `alpha` for why it is CSS. */
11
11
  export declare const tint: (color: string, percent: number) => string;
12
+ /** Which fill a label sits on, so a theme's `onPrimary` can answer for its own. */
13
+ export type FilledTextFill = "primary" | "other";
12
14
  /**
13
- * Text color for a label sitting on a brand-colored fill: the palette's
15
+ * Text color for a label sitting on a brand-colored fill: the theme's
16
+ * `onPrimary` on a primary fill when it sets one, else the palette's
14
17
  * contrasting end, `light` in light mode and `dark` in dark mode.
15
18
  */
16
- export declare const filledTextColor: (theme: Theme) => string;
19
+ export declare const filledTextColor: (theme: Theme, fill?: FilledTextFill) => string;
17
20
  /**
18
21
  * The same choice restated as a rule scoped to a `dark` class on <html>, which
19
22
  * is what apps resolving the mode in CSS key off before hydration can swap the
20
23
  * theme object. Additive: with an already-dark theme object both paths resolve
21
- * to the same color. Interpolate alongside `filledTextColor`.
24
+ * to the same color. A dark theme object's `onPrimary` answers for a primary
25
+ * fill; a light one under the class cannot know the dark value, so it keeps
26
+ * the palette's `dark`. Interpolate alongside `filledTextColor`.
22
27
  */
23
- export declare const darkFilledTextRule: (theme: Theme) => string;
28
+ export declare const darkFilledTextRule: (theme: Theme, fill?: FilledTextFill) => string;
24
29
  /**
25
30
  * Slim, theme-aware scrollbar for internal scroll areas (chat transcripts, code
26
31
  * blocks, tables) so the chunky native bar doesn't stand out, especially in
@@ -11,15 +11,18 @@ import { css } from "styled-components";
11
11
  /** Darkens a color by mixing it toward black. See `alpha` for why it is CSS. */ var shade = (color, percent) => `color-mix(in srgb, ${color} ${100 - percent}%, black)`;
12
12
  /** Lightens a color by mixing it toward white. See `alpha` for why it is CSS. */ var tint = (color, percent) => `color-mix(in srgb, ${color} ${100 - percent}%, white)`;
13
13
  /**
14
- * Text color for a label sitting on a brand-colored fill: the palette's
14
+ * Text color for a label sitting on a brand-colored fill: the theme's
15
+ * `onPrimary` on a primary fill when it sets one, else the palette's
15
16
  * contrasting end, `light` in light mode and `dark` in dark mode.
16
- */ var filledTextColor = (theme) => theme.isDark ? theme.colors.dark : theme.colors.light;
17
+ */ var filledTextColor = (theme, fill = "other") => fill === "primary" && theme.colors.onPrimary ? theme.colors.onPrimary : theme.isDark ? theme.colors.dark : theme.colors.light;
17
18
  /**
18
19
  * The same choice restated as a rule scoped to a `dark` class on <html>, which
19
20
  * is what apps resolving the mode in CSS key off before hydration can swap the
20
21
  * theme object. Additive: with an already-dark theme object both paths resolve
21
- * to the same color. Interpolate alongside `filledTextColor`.
22
- */ var darkFilledTextRule = (theme) => `:root.dark & { color: ${theme.colors.dark}; }`;
22
+ * to the same color. A dark theme object's `onPrimary` answers for a primary
23
+ * fill; a light one under the class cannot know the dark value, so it keeps
24
+ * the palette's `dark`. Interpolate alongside `filledTextColor`.
25
+ */ var darkFilledTextRule = (theme, fill = "other") => `:root.dark & { color: ${fill === "primary" && theme.isDark && theme.colors.onPrimary ? theme.colors.onPrimary : theme.colors.dark}; }`;
23
26
  /**
24
27
  * Slim, theme-aware scrollbar for internal scroll areas (chat transcripts, code
25
28
  * blocks, tables) so the chunky native bar doesn't stand out, especially in
@@ -57,6 +57,14 @@ export interface Colors<TString = string> {
57
57
  info: TString;
58
58
  dark: TString;
59
59
  light: TString;
60
+ /**
61
+ * Optional label color for text on a `primary` fill (a primary button, a
62
+ * sent chat bubble, a primary avatar). Set it when neither `light` nor
63
+ * `dark` reads on the theme's primary, e.g. a bright dark-mode primary
64
+ * that a light label cannot sit on. Without it the fill takes the palette's
65
+ * contrasting end, as `filledTextColor` describes.
66
+ */
67
+ onPrimary?: TString;
60
68
  }
61
69
  export interface Shadows<TString = string> {
62
70
  xs: TString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cherry-styled-components",
3
- "version": "0.2.19",
3
+ "version": "0.2.21",
4
4
  "description": "Cherry is a design system for the modern web. Designed in Figma, built in React using Typescript.",
5
5
  "private": false,
6
6
  "type": "module",