cherry-styled-components 0.2.1 → 0.2.3

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/modal.d.ts CHANGED
@@ -5,6 +5,15 @@ export interface ModalProps {
5
5
  $onClose: () => void;
6
6
  $title?: string;
7
7
  $width?: number;
8
+ $hideCloseButton?: boolean;
9
+ /** Escape hatches for app-level restyling, both applied to the overlay
10
+ root. Note that wrapping with styled(Modal) does NOT work for the $-props
11
+ API (styled-components strips transient props before they reach the
12
+ wrapped component), so restyle by targeting a passed class instead. The
13
+ inner parts expose stable class hooks: .modal-inner, .modal-close,
14
+ .modal-title, .modal-content. */
15
+ className?: string;
16
+ style?: React.CSSProperties;
8
17
  }
9
- declare function Modal({ children, $isOpen, $onClose, $title, $width }: ModalProps): React.ReactPortal | null;
18
+ declare function Modal({ children, $isOpen, $onClose, $title, $width, $hideCloseButton, className, style, }: ModalProps): React.ReactPortal | null;
10
19
  export { Modal };
package/dist/modal.js CHANGED
@@ -6,8 +6,8 @@ import { useOnClickOutside } from "./utils/use-on-click-outside.js";
6
6
  import { Icon } from "./icon.js";
7
7
  import { IconButton } from "./icon-button.js";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
- import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
10
- import styled, { css } from "styled-components";
9
+ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
10
+ import styled, { css, keyframes } from "styled-components";
11
11
  import { rgba } from "polished";
12
12
  import { createPortal } from "react-dom";
13
13
  //#region src/lib/modal.tsx
@@ -15,26 +15,31 @@ var emptySubscribe = () => () => {};
15
15
  function useIsClient() {
16
16
  return useSyncExternalStore(emptySubscribe, () => true, () => false);
17
17
  }
18
+ var overlayFadeIn = keyframes([`from{opacity:0;}to{opacity:1;}`]);
19
+ var overlayFadeOut = keyframes([`from{opacity:1;}to{opacity:0;}`]);
20
+ var innerRiseIn = keyframes([`from{transform:translateY(40px);}to{transform:translateY(0);}`]);
21
+ var innerSinkOut = keyframes([`from{transform:translateY(0);}to{transform:translateY(40px);}`]);
18
22
  var StyledModal = styled.div.withConfig({
19
23
  displayName: "modal__StyledModal",
20
- componentId: "sc-43ca5273-0"
24
+ componentId: "sc-14a8d5c-0"
21
25
  })([
22
26
  `position:fixed;top:0;left:0;width:100%;height:100%;background:`,
23
- `;display:flex;justify-content:center;align-items:center;z-index:1010;pointer-events:none;opacity:0;transition:all 0.3s ease;`,
27
+ `;display:flex;justify-content:center;align-items:center;z-index:1010;animation:`,
28
+ ` 0.3s ease both;`,
24
29
  ` & .modal-inner{background:`,
25
30
  `;border-radius:`,
26
- `;padding:20px;max-width:calc(100% - 40px);width:100%;margin:auto;position:relative;transform:translateY(40px);transition:all 0.3s ease;`,
27
- ` `,
31
+ `;padding:20px;max-width:calc(100% - 40px);width:100%;margin:auto;position:relative;animation:`,
32
+ ` 0.3s ease both;`,
28
33
  `{max-width:500px;`,
29
34
  `}}`
30
- ], ({ theme }) => rgba(theme.colors.primary, .5), ({ $isOpen }) => $isOpen && css([`opacity:1;pointer-events:all;`]), ({ theme }) => theme.colors.light, ({ theme }) => theme.spacing.radius.lg, ({ $isOpen }) => $isOpen && css([`transform:translateY(0);`]), mq("lg"), ({ $width }) => $width && css([`max-width:`, `px;`], $width));
35
+ ], ({ theme }) => rgba(theme.colors.primary, .5), ({ $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));
31
36
  var StyledModalClose = styled(IconButton).withConfig({
32
37
  displayName: "modal__StyledModalClose",
33
- componentId: "sc-43ca5273-1"
38
+ componentId: "sc-14a8d5c-1"
34
39
  })([`position:absolute;top:20px;right:20px;z-index:10;`]);
35
40
  var StyledModalTitle = styled.h2.withConfig({
36
41
  displayName: "modal__StyledModalTitle",
37
- componentId: "sc-43ca5273-2"
42
+ componentId: "sc-14a8d5c-2"
38
43
  })([
39
44
  `--divider-color:`,
40
45
  `;margin:0 0 15px 0;padding:0 0 15px 0;color:`,
@@ -43,16 +48,19 @@ var StyledModalTitle = styled.h2.withConfig({
43
48
  ], ({ theme }) => theme.colors.grayLight, ({ theme }) => theme.colors.dark, ({ theme }) => styledH5(theme));
44
49
  var StyledModalContent = styled.div.withConfig({
45
50
  displayName: "modal__StyledModalContent",
46
- componentId: "sc-43ca5273-3"
51
+ componentId: "sc-14a8d5c-3"
47
52
  })([
48
53
  `max-height:calc(100svh - 200px);overflow-y:auto;padding:5px;margin:-5px;--divider-color:`,
49
54
  `;`,
50
55
  `;& hr{margin:20px 0;border:none;border-bottom:solid 1px var(--divider-color);}`
51
56
  ], ({ theme }) => theme.colors.grayLight, ({ theme }) => styledText(theme));
52
- function Modal({ children, $isOpen, $onClose, $title, $width }) {
57
+ function Modal({ children, $isOpen, $onClose, $title, $width, $hideCloseButton, className, style }) {
53
58
  const wrapperRef = useRef(null);
54
59
  const elmRef = useRef(null);
55
60
  const isClient = useIsClient();
61
+ const [shouldRender, setShouldRender] = useState($isOpen);
62
+ if ($isOpen && !shouldRender) setShouldRender(true);
63
+ const isClosing = !$isOpen && shouldRender;
56
64
  const closeModal = useCallback(() => {
57
65
  $onClose();
58
66
  }, [$onClose]);
@@ -68,15 +76,25 @@ function Modal({ children, $isOpen, $onClose, $title, $width }) {
68
76
  return () => document.removeEventListener("keydown", handleKeydown);
69
77
  }, [$isOpen, closeModal]);
70
78
  useOnClickOutside([elmRef, wrapperRef], $isOpen ? closeModal : () => {});
71
- if (!isClient) return null;
79
+ const handleAnimationEnd = useCallback((event) => {
80
+ if (event.target !== event.currentTarget) return;
81
+ if (!$isOpen) setShouldRender(false);
82
+ }, [$isOpen]);
83
+ if (!isClient || !shouldRender) return null;
72
84
  return /*#__PURE__*/ createPortal(/*#__PURE__*/ jsx(StyledModal, {
73
- $isOpen,
85
+ $isClosing: isClosing,
74
86
  $width,
87
+ className,
88
+ style,
89
+ onAnimationEnd: handleAnimationEnd,
75
90
  children: /*#__PURE__*/ jsxs("div", {
76
91
  className: "modal-inner",
77
92
  ref: wrapperRef,
93
+ role: "dialog",
94
+ "aria-modal": "true",
95
+ "aria-label": $title,
78
96
  children: [
79
- /*#__PURE__*/ jsx("span", {
97
+ !$hideCloseButton && /*#__PURE__*/ jsx("span", {
80
98
  ref: elmRef,
81
99
  children: /*#__PURE__*/ jsx(StyledModalClose, {
82
100
  $size: "small",
@@ -86,8 +104,14 @@ function Modal({ children, $isOpen, $onClose, $title, $width }) {
86
104
  children: /*#__PURE__*/ jsx(Icon, { name: "X" })
87
105
  })
88
106
  }),
89
- $title && /*#__PURE__*/ jsx(StyledModalTitle, { children: $title }),
90
- /*#__PURE__*/ jsx(StyledModalContent, { children })
107
+ $title && /*#__PURE__*/ jsx(StyledModalTitle, {
108
+ className: "modal-title",
109
+ children: $title
110
+ }),
111
+ /*#__PURE__*/ jsx(StyledModalContent, {
112
+ className: "modal-content",
113
+ children
114
+ })
91
115
  ]
92
116
  })
93
117
  }), document.body);
@@ -1,17 +1,28 @@
1
1
  "use client";
2
2
  "use client";
3
- import { useEffect } from "react";
3
+ import { useEffect, useRef } from "react";
4
4
  //#region src/lib/utils/use-on-click-outside.tsx
5
5
  function useOnClickOutside(refs, cb) {
6
+ const latest = useRef({
7
+ refs,
8
+ cb
9
+ });
10
+ useEffect(() => {
11
+ latest.current = {
12
+ refs,
13
+ cb
14
+ };
15
+ });
6
16
  useEffect(() => {
7
17
  function handleClickOutside(event) {
8
- if (refs && refs.map((ref) => ref && ref.current && ref.current.contains(event.target)).every((i) => i === false)) cb();
18
+ const { refs, cb } = latest.current;
19
+ if (refs.every((ref) => !ref.current || !ref.current.contains(event.target))) cb();
9
20
  }
10
21
  document.addEventListener("mousedown", handleClickOutside);
11
22
  return () => {
12
23
  document.removeEventListener("mousedown", handleClickOutside);
13
24
  };
14
- }, [refs, cb]);
25
+ }, []);
15
26
  }
16
27
  //#endregion
17
28
  export { useOnClickOutside };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cherry-styled-components",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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",