@grantbii/design-system 2.0.0 → 3.0.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/core/molecules/Badge.js +6 -9
- package/core/molecules/Badge.js.map +1 -1
- package/core/molecules/Toast.d.ts +20 -0
- package/core/molecules/Toast.js +239 -0
- package/core/molecules/Toast.js.map +1 -0
- package/core/molecules/Tooltip.d.ts +20 -0
- package/core/molecules/Tooltip.js +182 -0
- package/core/molecules/Tooltip.js.map +1 -0
- package/core/molecules/index.d.ts +2 -0
- package/core/molecules/index.js +2 -0
- package/core/molecules/index.js.map +1 -1
- package/core/organisms/Dropdown.d.ts +13 -7
- package/core/organisms/Dropdown.js +111 -12
- package/core/organisms/Dropdown.js.map +1 -1
- package/package.json +12 -12
- package/stories/molecules/Toast.stories.d.ts +9 -0
- package/stories/molecules/Toast.stories.js +106 -0
- package/stories/molecules/Toast.stories.js.map +1 -0
- package/stories/molecules/Tooltip.stories.d.ts +11 -0
- package/stories/molecules/Tooltip.stories.js +33 -0
- package/stories/molecules/Tooltip.stories.js.map +1 -0
- package/stories/organisms/Dropdown.stories.d.ts +4 -2
- package/stories/organisms/Dropdown.stories.js +75 -18
- package/stories/organisms/Dropdown.stories.js.map +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/core/molecules/Badge.js
CHANGED
|
@@ -44,17 +44,14 @@ const BaseBadge = styled.div `
|
|
|
44
44
|
display: flex;
|
|
45
45
|
align-items: center;
|
|
46
46
|
justify-content: space-between;
|
|
47
|
-
gap:
|
|
47
|
+
gap: ${Spacing.px12};
|
|
48
48
|
|
|
49
49
|
height: 26px;
|
|
50
50
|
padding: 5px ${Spacing.px12};
|
|
51
|
-
border-radius: 100px;
|
|
52
|
-
|
|
53
|
-
font-size: 12px;
|
|
54
|
-
font-weight: ${Typography.weight.medium};
|
|
55
51
|
|
|
56
52
|
color: ${(props) => props.$color};
|
|
57
53
|
background-color: ${(props) => props.$defaultBackgroundColor};
|
|
54
|
+
border-radius: ${Spacing.px100};
|
|
58
55
|
|
|
59
56
|
cursor: ${(props) => (props.$isClickable ? "pointer" : "auto")};
|
|
60
57
|
|
|
@@ -67,10 +64,10 @@ const BaseBadge = styled.div `
|
|
|
67
64
|
const BadgeContent = styled.div `
|
|
68
65
|
display: flex;
|
|
69
66
|
align-items: center;
|
|
70
|
-
gap:
|
|
67
|
+
gap: ${Spacing.px4};
|
|
71
68
|
|
|
72
69
|
width: ${({ $widthPixels }) => ($widthPixels ? `${$widthPixels}px` : "auto")};
|
|
73
|
-
max-width: ${({ $hasX: $closeable }) => $closeable ?
|
|
70
|
+
max-width: ${({ $hasX: $closeable }) => $closeable ? `calc(100% - ${Spacing.px20})` : "auto"};
|
|
74
71
|
`;
|
|
75
72
|
const IconContainer = styled.div `
|
|
76
73
|
display: flex;
|
|
@@ -85,13 +82,13 @@ const BadgeLabel = styled.div `
|
|
|
85
82
|
white-space: nowrap;
|
|
86
83
|
text-overflow: ellipsis;
|
|
87
84
|
|
|
88
|
-
${applyTypography(Typography.
|
|
85
|
+
${applyTypography(Typography.captionMedium)}
|
|
89
86
|
`;
|
|
90
87
|
const XButton = ({ onClick }) => (_jsx(BaseCloseButton, { type: "button", onClick: onClick, children: _jsx(SystemIcon.XIcon, { color: Color.typography.blackMedium, size: 12 }) }));
|
|
91
88
|
const BaseCloseButton = styled.button `
|
|
92
89
|
display: flex;
|
|
93
90
|
flex-direction: column;
|
|
94
91
|
|
|
95
|
-
min-width:
|
|
92
|
+
min-width: ${Spacing.px12};
|
|
96
93
|
`;
|
|
97
94
|
//# sourceMappingURL=Badge.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../../core/molecules/Badge.tsx"],"names":[],"mappings":";AACA,OAAO,MAAM,MAAM,mBAAmB,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAgBlD,MAAM,KAAK,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS,EAAE,GAAG,WAAW,EAAc,EAAE,EAAE;IACpE,MAAM,YAAY,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAChD,OAAO,KAAC,QAAQ,OAAK,YAAY,KAAM,WAAW,GAAI,CAAC;AACzD,CAAC,CAAC;AAEF,eAAe,KAAK,CAAC;AAQrB,MAAM,iBAAiB,GAAqD;IAC1E,OAAO,EAAE;QACP,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,SAAS;QACjC,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK;QAC1C,oBAAoB,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK;KACzC;IACD,OAAO,EAAE;QACP,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,SAAS;QACjC,sBAAsB,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK;QAC3C,oBAAoB,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK;KAC1C;IACD,IAAI,EAAE;QACJ,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK;QACzB,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK;QAC1C,oBAAoB,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK;KACzC;IACD,KAAK,EAAE;QACL,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;QAC1B,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;QAC3C,oBAAoB,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;KAC1C;IACD,MAAM,EAAE;QACN,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;QAC3B,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;QAC5C,oBAAoB,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;KAC3C;IACD,GAAG,EAAE;QACH,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI;QACxB,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI;QACzC,oBAAoB,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI;KACxC;CACF,CAAC;AAIF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EACvB,KAAK,EACL,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,GAAG,sBAAsB,EAC7C,KAAK,GACS,EAAE,EAAE,CAAC,CACnB,MAAC,SAAS,IACR,OAAO,EAAE,OAAO,kBACF,CAAC,CAAC,OAAO,6BACE,sBAAsB,2BACxB,oBAAoB,YACnC,KAAK,aAEb,MAAC,YAAY,aAAQ,CAAC,CAAC,QAAQ,kBAAgB,gBAAgB,aAC5D,IAAI,CAAC,CAAC,CAAC,CACN,KAAC,aAAa,cACZ,KAAC,IAAI,IAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,GAAI,GAClB,CACjB,CAAC,CAAC,CAAC,CACF,mBAAK,CACN,EAED,KAAC,UAAU,cAAE,KAAK,GAAc,IACnB,EAEd,QAAQ,CAAC,CAAC,CAAC,KAAC,OAAO,IAAC,OAAO,EAAE,QAAQ,GAAI,CAAC,CAAC,CAAC,mBAAK,IACxC,CACb,CAAC;AAEF,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAK1B
|
|
1
|
+
{"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../../core/molecules/Badge.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentType, MouseEventHandler, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { Color, Spacing, SystemIcon, Typography } from \"../atoms\";\nimport { applyTypography } from \"../integrations\";\n\ntype BadgeVariant = \"default\" | \"neutral\" | \"blue\" | \"green\" | \"yellow\" | \"red\";\n\ntype CustomBadgeProps = {\n label: ReactNode;\n Icon?: ComponentType<SystemIcon.IconProps>;\n onClick?: MouseEventHandler<HTMLDivElement>;\n onClickX?: MouseEventHandler<HTMLButtonElement>;\n labelWidthPixels?: number;\n};\n\ntype BadgeProps = {\n variant?: BadgeVariant;\n} & CustomBadgeProps;\n\nconst Badge = ({ variant = \"default\", ...restOfProps }: BadgeProps) => {\n const variantProps = VARIANT_PROPS_MAP[variant];\n return <RawBadge {...variantProps} {...restOfProps} />;\n};\n\nexport default Badge;\n\ntype VariantStyleProps = {\n color: Color.DesignColor;\n defaultBackgroundColor: Color.DesignColor;\n hoverBackgroundColor?: Color.DesignColor;\n};\n\nconst VARIANT_PROPS_MAP: { [variant in BadgeVariant]: VariantStyleProps } = {\n default: {\n color: Color.typography.blackHigh,\n defaultBackgroundColor: Color.accent.blue3,\n hoverBackgroundColor: Color.accent.blue4,\n },\n neutral: {\n color: Color.typography.blackHigh,\n defaultBackgroundColor: Color.neutral.grey4,\n hoverBackgroundColor: Color.neutral.grey3,\n },\n blue: {\n color: Color.accent.blue1,\n defaultBackgroundColor: Color.accent.blue3,\n hoverBackgroundColor: Color.accent.blue4,\n },\n green: {\n color: Color.accent.green1,\n defaultBackgroundColor: Color.accent.green3,\n hoverBackgroundColor: Color.accent.green4,\n },\n yellow: {\n color: Color.accent.yellow1,\n defaultBackgroundColor: Color.accent.yellow3,\n hoverBackgroundColor: Color.accent.yellow4,\n },\n red: {\n color: Color.accent.red1,\n defaultBackgroundColor: Color.accent.red3,\n hoverBackgroundColor: Color.accent.red4,\n },\n};\n\ntype RawBadgeProps = CustomBadgeProps & VariantStyleProps;\n\nexport const RawBadge = ({\n label,\n Icon,\n onClick,\n onClickX,\n labelWidthPixels,\n defaultBackgroundColor,\n hoverBackgroundColor = defaultBackgroundColor,\n color,\n}: RawBadgeProps) => (\n <BaseBadge\n onClick={onClick}\n $isClickable={!!onClick}\n $defaultBackgroundColor={defaultBackgroundColor}\n $hoverBackgroundColor={hoverBackgroundColor}\n $color={color}\n >\n <BadgeContent $hasX={!!onClickX} $widthPixels={labelWidthPixels}>\n {Icon ? (\n <IconContainer>\n <Icon color={color} size={12} />\n </IconContainer>\n ) : (\n <></>\n )}\n\n <BadgeLabel>{label}</BadgeLabel>\n </BadgeContent>\n\n {onClickX ? <XButton onClick={onClickX} /> : <></>}\n </BaseBadge>\n);\n\nconst BaseBadge = styled.div<{\n $isClickable: boolean;\n $defaultBackgroundColor: Color.DesignColor;\n $hoverBackgroundColor: Color.DesignColor;\n $color: Color.DesignColor;\n}>`\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: ${Spacing.px12};\n\n height: 26px;\n padding: 5px ${Spacing.px12};\n\n color: ${(props) => props.$color};\n background-color: ${(props) => props.$defaultBackgroundColor};\n border-radius: ${Spacing.px100};\n\n cursor: ${(props) => (props.$isClickable ? \"pointer\" : \"auto\")};\n\n &:hover {\n background-color: ${(props) =>\n props.$isClickable\n ? props.$hoverBackgroundColor\n : props.$defaultBackgroundColor};\n }\n`;\n\nconst BadgeContent = styled.div<{\n $hasX: boolean;\n $widthPixels?: number;\n}>`\n display: flex;\n align-items: center;\n gap: ${Spacing.px4};\n\n width: ${({ $widthPixels }) => ($widthPixels ? `${$widthPixels}px` : \"auto\")};\n max-width: ${({ $hasX: $closeable }) =>\n $closeable ? `calc(100% - ${Spacing.px20})` : \"auto\"};\n`;\n\nconst IconContainer = styled.div<{ $iconSize?: string | number }>`\n display: flex;\n flex-direction: column;\n\n width: ${({ $iconSize = \"auto\" }) => $iconSize};\n min-width: ${({ $iconSize = \"auto\" }) => $iconSize};\n max-width: ${({ $iconSize = \"auto\" }) => $iconSize};\n`;\n\nconst BadgeLabel = styled.div`\n overflow-x: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n\n ${applyTypography(Typography.captionMedium)}\n`;\n\ntype XButtonProps = {\n onClick: MouseEventHandler<HTMLButtonElement>;\n};\n\nconst XButton = ({ onClick }: XButtonProps) => (\n <BaseCloseButton type=\"button\" onClick={onClick}>\n <SystemIcon.XIcon color={Color.typography.blackMedium} size={12} />\n </BaseCloseButton>\n);\n\nconst BaseCloseButton = styled.button`\n display: flex;\n flex-direction: column;\n\n min-width: ${Spacing.px12};\n`;\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type HTMLAttributes, type PropsWithChildren, type ReactNode } from "react";
|
|
2
|
+
export type ToastVariant = "neutral" | "success" | "danger";
|
|
3
|
+
export type ToastProps = Omit<HTMLAttributes<HTMLDivElement>, "children" | "title"> & {
|
|
4
|
+
title?: ReactNode;
|
|
5
|
+
message?: ReactNode;
|
|
6
|
+
variant?: ToastVariant;
|
|
7
|
+
action?: {
|
|
8
|
+
label: ReactNode;
|
|
9
|
+
onClick: () => void;
|
|
10
|
+
};
|
|
11
|
+
onClose?: () => void;
|
|
12
|
+
};
|
|
13
|
+
declare const Toast: ({ title, message, variant, action, onClose, ...props }: ToastProps) => import("react").JSX.Element;
|
|
14
|
+
export default Toast;
|
|
15
|
+
export type ToastOptions = ToastProps & {
|
|
16
|
+
dedupeKey?: string;
|
|
17
|
+
duration?: number;
|
|
18
|
+
};
|
|
19
|
+
export declare const ToastProvider: ({ children }: PropsWithChildren) => import("react").JSX.Element;
|
|
20
|
+
export declare const useToast: () => (options: ToastOptions) => void;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext, useEffect, useState, } from "react";
|
|
3
|
+
import styled, { keyframes } from "styled-components";
|
|
4
|
+
import { Color, Responsive, Spacing, SystemIcon, Typography } from "../atoms";
|
|
5
|
+
import { applyTypography } from "../integrations";
|
|
6
|
+
const toastVariants = {
|
|
7
|
+
neutral: {
|
|
8
|
+
iconBackground: Color.neutral.grey1,
|
|
9
|
+
border: Color.neutral.grey2,
|
|
10
|
+
background: Color.neutral.grey3,
|
|
11
|
+
Icon: null,
|
|
12
|
+
},
|
|
13
|
+
success: {
|
|
14
|
+
iconBackground: Color.accent.green1,
|
|
15
|
+
border: Color.accent.green2,
|
|
16
|
+
background: Color.accent.green4,
|
|
17
|
+
Icon: SystemIcon.CheckIcon,
|
|
18
|
+
},
|
|
19
|
+
danger: {
|
|
20
|
+
iconBackground: Color.accent.red1,
|
|
21
|
+
border: Color.accent.red2,
|
|
22
|
+
background: Color.accent.red4,
|
|
23
|
+
Icon: SystemIcon.XIcon,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const Toast = ({ title, message, variant = "neutral", action, onClose, ...props }) => {
|
|
27
|
+
const { iconBackground, border, background, Icon } = toastVariants[variant];
|
|
28
|
+
const isCompact = title == null && !action;
|
|
29
|
+
return (_jsxs(ToastContainer, { ...props, role: variant === "danger" ? "alert" : "status", "$border": border, "$background": background, "$compact": isCompact, children: [_jsxs(ToastLayout, { children: [_jsxs(ToastDetails, { children: [_jsx(ToastIconContainer, { "$background": iconBackground, children: Icon ? (_jsx(Icon, { color: Color.neutral.white, size: 14, weight: "bold" })) : (_jsx(SystemIcon.InfoIcon, { color: Color.neutral.white, size: 14, weight: "bold" })) }), _jsxs(ToastText, { children: [title != null && _jsx(Title, { children: title }), message != null && _jsx(Message, { children: message })] })] }), action && (_jsx(ActionButton, { type: "button", onClick: action.onClick, children: action.label }))] }), onClose && (_jsx(CloseButton, { type: "button", onClick: onClose, children: _jsx(SystemIcon.XIcon, { color: Color.typography.blackMedium, size: 18 }) }))] }));
|
|
30
|
+
};
|
|
31
|
+
export default Toast;
|
|
32
|
+
const ToastContext = createContext(null);
|
|
33
|
+
export const ToastProvider = ({ children }) => {
|
|
34
|
+
const [toasts, setToasts] = useState([]);
|
|
35
|
+
const dismissToast = useCallback((id) => setToasts((currentToasts) => currentToasts.filter((toast) => toast.id !== id)), []);
|
|
36
|
+
const showToast = useCallback((options) => setToasts((currentToasts) => {
|
|
37
|
+
const existingToastIndex = options.dedupeKey
|
|
38
|
+
? currentToasts.findIndex((toast) => toast.dedupeKey === options.dedupeKey)
|
|
39
|
+
: -1;
|
|
40
|
+
const nextToast = existingToastIndex < 0
|
|
41
|
+
? { ...options, id: crypto.randomUUID(), revision: 0 }
|
|
42
|
+
: {
|
|
43
|
+
...currentToasts[existingToastIndex],
|
|
44
|
+
...options,
|
|
45
|
+
revision: currentToasts[existingToastIndex].revision + 1,
|
|
46
|
+
};
|
|
47
|
+
return [
|
|
48
|
+
nextToast,
|
|
49
|
+
...currentToasts.filter((_, toastIndex) => toastIndex !== existingToastIndex),
|
|
50
|
+
].slice(0, 3);
|
|
51
|
+
}), []);
|
|
52
|
+
return (_jsxs(ToastContext.Provider, { value: showToast, children: [children, _jsx(Viewport, { "data-slot": "toast-viewport", "$stackDepth": Math.max(0, toasts.length - 1), children: toasts.map(({ id, revision, dedupeKey, duration = 8000, ...props }, index) => (_jsx(StackItem, { "data-pulse": revision > 0, "$index": index, children: _jsx(TimedToast, { ...props, id: id, duration: duration, dismiss: dismissToast }) }, `${dedupeKey ?? id}-${revision}`))) })] }));
|
|
53
|
+
};
|
|
54
|
+
export const useToast = () => {
|
|
55
|
+
const showToast = useContext(ToastContext);
|
|
56
|
+
if (!showToast)
|
|
57
|
+
throw new Error("useToast must be used within ToastProvider");
|
|
58
|
+
return showToast;
|
|
59
|
+
};
|
|
60
|
+
const TimedToast = ({ id, duration, dismiss, action, onClose, onMouseEnter, onMouseLeave, onFocus, onBlur, ...props }) => {
|
|
61
|
+
const [isTimerPaused, setIsTimerPaused] = useState(false);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (isTimerPaused)
|
|
64
|
+
return;
|
|
65
|
+
const timeoutId = window.setTimeout(() => dismiss(id), duration);
|
|
66
|
+
return () => window.clearTimeout(timeoutId);
|
|
67
|
+
}, [dismiss, duration, id, isTimerPaused]);
|
|
68
|
+
return (_jsx(Toast, { ...props, onMouseEnter: (event) => {
|
|
69
|
+
setIsTimerPaused(true);
|
|
70
|
+
onMouseEnter?.(event);
|
|
71
|
+
}, onMouseLeave: (event) => {
|
|
72
|
+
if (!event.currentTarget.contains(document.activeElement))
|
|
73
|
+
setIsTimerPaused(false);
|
|
74
|
+
onMouseLeave?.(event);
|
|
75
|
+
}, onFocus: (event) => {
|
|
76
|
+
setIsTimerPaused(true);
|
|
77
|
+
onFocus?.(event);
|
|
78
|
+
}, onBlur: (event) => {
|
|
79
|
+
if (!event.currentTarget.contains(event.relatedTarget) &&
|
|
80
|
+
!event.currentTarget.matches(":hover"))
|
|
81
|
+
setIsTimerPaused(false);
|
|
82
|
+
onBlur?.(event);
|
|
83
|
+
}, action: action && {
|
|
84
|
+
label: action.label,
|
|
85
|
+
onClick: () => {
|
|
86
|
+
dismiss(id);
|
|
87
|
+
action.onClick();
|
|
88
|
+
},
|
|
89
|
+
}, onClose: () => {
|
|
90
|
+
onClose?.();
|
|
91
|
+
dismiss(id);
|
|
92
|
+
} }));
|
|
93
|
+
};
|
|
94
|
+
const ToastContainer = styled.div `
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
gap: ${Spacing.px16};
|
|
98
|
+
|
|
99
|
+
box-sizing: border-box;
|
|
100
|
+
width: ${({ $compact }) => ($compact ? "300px" : "400px")};
|
|
101
|
+
max-width: calc(100vw - ${Spacing.px40});
|
|
102
|
+
min-height: ${({ $compact }) => ($compact ? "44px" : "64px")};
|
|
103
|
+
padding: ${Spacing.px12};
|
|
104
|
+
|
|
105
|
+
background: ${({ $background }) => $background};
|
|
106
|
+
border: 1px solid ${({ $border }) => $border};
|
|
107
|
+
border-radius: ${Spacing.px8};
|
|
108
|
+
`;
|
|
109
|
+
const ToastLayout = styled.div `
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: flex-start;
|
|
112
|
+
gap: ${Spacing.px8};
|
|
113
|
+
flex: 1;
|
|
114
|
+
|
|
115
|
+
min-width: 0;
|
|
116
|
+
`;
|
|
117
|
+
const ToastDetails = styled.div `
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: flex-start;
|
|
120
|
+
gap: ${Spacing.px12};
|
|
121
|
+
flex: 1;
|
|
122
|
+
|
|
123
|
+
min-width: 0;
|
|
124
|
+
`;
|
|
125
|
+
const ToastIconContainer = styled.span `
|
|
126
|
+
display: grid;
|
|
127
|
+
place-items: center;
|
|
128
|
+
flex: 0 0 ${Spacing.px20};
|
|
129
|
+
|
|
130
|
+
height: ${Spacing.px20};
|
|
131
|
+
|
|
132
|
+
background: ${({ $background }) => $background};
|
|
133
|
+
border-radius: ${Spacing.px4};
|
|
134
|
+
`;
|
|
135
|
+
const ToastText = styled.div `
|
|
136
|
+
display: flex;
|
|
137
|
+
flex: 1;
|
|
138
|
+
flex-direction: column;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
|
|
141
|
+
min-width: 0;
|
|
142
|
+
`;
|
|
143
|
+
const Title = styled.div `
|
|
144
|
+
color: ${Color.typography.blackHigh};
|
|
145
|
+
|
|
146
|
+
${applyTypography(Typography.bodySecondaryBold)}
|
|
147
|
+
`;
|
|
148
|
+
const Message = styled.div `
|
|
149
|
+
color: ${Color.typography.blackMedium};
|
|
150
|
+
|
|
151
|
+
${applyTypography(Typography.bodySecondaryRegular)}
|
|
152
|
+
`;
|
|
153
|
+
const ActionButton = styled.button `
|
|
154
|
+
align-self: center;
|
|
155
|
+
flex: none;
|
|
156
|
+
|
|
157
|
+
box-sizing: border-box;
|
|
158
|
+
height: ${Spacing.px40};
|
|
159
|
+
padding: 10px 0;
|
|
160
|
+
|
|
161
|
+
color: ${Color.typography.blackHigh};
|
|
162
|
+
background: transparent;
|
|
163
|
+
|
|
164
|
+
text-decoration: underline;
|
|
165
|
+
white-space: nowrap;
|
|
166
|
+
|
|
167
|
+
cursor: pointer;
|
|
168
|
+
|
|
169
|
+
${applyTypography(Typography.bodySecondaryMedium)}
|
|
170
|
+
`;
|
|
171
|
+
const CloseButton = styled.button `
|
|
172
|
+
display: grid;
|
|
173
|
+
place-items: center;
|
|
174
|
+
align-self: center;
|
|
175
|
+
flex: 0 0 ${Spacing.px24};
|
|
176
|
+
|
|
177
|
+
width: ${Spacing.px24};
|
|
178
|
+
height: ${Spacing.px24};
|
|
179
|
+
|
|
180
|
+
background: transparent;
|
|
181
|
+
|
|
182
|
+
cursor: pointer;
|
|
183
|
+
`;
|
|
184
|
+
const toastPulse = keyframes `
|
|
185
|
+
0%, 100% { transform: scale(1); }
|
|
186
|
+
50% { transform: scale(1.03); }
|
|
187
|
+
`;
|
|
188
|
+
const StackItem = styled.div `
|
|
189
|
+
grid-area: 1 / 1;
|
|
190
|
+
z-index: ${({ $index }) => 3 - $index};
|
|
191
|
+
|
|
192
|
+
transform: translate(
|
|
193
|
+
${({ $index }) => `${-$index * 8}px`},
|
|
194
|
+
${({ $index }) => `${-$index * 8}px`}
|
|
195
|
+
);
|
|
196
|
+
transition: transform 250ms ease-in-out;
|
|
197
|
+
pointer-events: ${({ $index }) => ($index === 0 ? "auto" : "none")};
|
|
198
|
+
|
|
199
|
+
${({ $index }) => $index > 0 &&
|
|
200
|
+
`
|
|
201
|
+
> [role] > * {
|
|
202
|
+
visibility: hidden;
|
|
203
|
+
}
|
|
204
|
+
`}
|
|
205
|
+
|
|
206
|
+
&[data-pulse="true"] > [role] {
|
|
207
|
+
animation: ${toastPulse} 250ms ease-in-out;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@media (prefers-reduced-motion: reduce) {
|
|
211
|
+
transition: none;
|
|
212
|
+
|
|
213
|
+
&[data-pulse="true"] > [role] {
|
|
214
|
+
animation: none;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
`;
|
|
218
|
+
const Viewport = styled.div `
|
|
219
|
+
display: grid;
|
|
220
|
+
padding: ${({ $stackDepth }) => `${$stackDepth * 8}px 0 0 ${$stackDepth * 8}px`};
|
|
221
|
+
|
|
222
|
+
z-index: 100;
|
|
223
|
+
position: fixed;
|
|
224
|
+
top: 25px;
|
|
225
|
+
right: ${Spacing.px20};
|
|
226
|
+
transition: padding 250ms ease-in-out;
|
|
227
|
+
|
|
228
|
+
@media (prefers-reduced-motion: reduce) {
|
|
229
|
+
transition: none;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@media (width < ${Responsive.widthBreakpoint.laptop}) {
|
|
233
|
+
top: auto;
|
|
234
|
+
right: 50%;
|
|
235
|
+
bottom: var(--toast-mobile-bottom-offset, ${Spacing.px88});
|
|
236
|
+
transform: translateX(50%);
|
|
237
|
+
}
|
|
238
|
+
`;
|
|
239
|
+
//# sourceMappingURL=Toast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Toast.js","sourceRoot":"","sources":["../../../core/molecules/Toast.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n createContext,\n type HTMLAttributes,\n type PropsWithChildren,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport styled, { keyframes } from \"styled-components\";\nimport { Color, Responsive, Spacing, SystemIcon, Typography } from \"../atoms\";\nimport { applyTypography } from \"../integrations\";\n\nexport type ToastVariant = \"neutral\" | \"success\" | \"danger\";\n\nexport type ToastProps = Omit<\n HTMLAttributes<HTMLDivElement>,\n \"children\" | \"title\"\n> & {\n title?: ReactNode;\n message?: ReactNode;\n variant?: ToastVariant;\n action?: {\n label: ReactNode;\n onClick: () => void;\n };\n onClose?: () => void;\n};\n\nconst toastVariants = {\n neutral: {\n iconBackground: Color.neutral.grey1,\n border: Color.neutral.grey2,\n background: Color.neutral.grey3,\n Icon: null,\n },\n success: {\n iconBackground: Color.accent.green1,\n border: Color.accent.green2,\n background: Color.accent.green4,\n Icon: SystemIcon.CheckIcon,\n },\n danger: {\n iconBackground: Color.accent.red1,\n border: Color.accent.red2,\n background: Color.accent.red4,\n Icon: SystemIcon.XIcon,\n },\n} as const;\n\nconst Toast = ({\n title,\n message,\n variant = \"neutral\",\n action,\n onClose,\n ...props\n}: ToastProps) => {\n const { iconBackground, border, background, Icon } = toastVariants[variant];\n const isCompact = title == null && !action;\n\n return (\n <ToastContainer\n {...props}\n role={variant === \"danger\" ? \"alert\" : \"status\"}\n $border={border}\n $background={background}\n $compact={isCompact}\n >\n <ToastLayout>\n <ToastDetails>\n <ToastIconContainer $background={iconBackground}>\n {Icon ? (\n <Icon color={Color.neutral.white} size={14} weight=\"bold\" />\n ) : (\n <SystemIcon.InfoIcon\n color={Color.neutral.white}\n size={14}\n weight=\"bold\"\n />\n )}\n </ToastIconContainer>\n <ToastText>\n {title != null && <Title>{title}</Title>}\n {message != null && <Message>{message}</Message>}\n </ToastText>\n </ToastDetails>\n {action && (\n <ActionButton type=\"button\" onClick={action.onClick}>\n {action.label}\n </ActionButton>\n )}\n </ToastLayout>\n {onClose && (\n <CloseButton type=\"button\" onClick={onClose}>\n <SystemIcon.XIcon color={Color.typography.blackMedium} size={18} />\n </CloseButton>\n )}\n </ToastContainer>\n );\n};\n\nexport default Toast;\n\nexport type ToastOptions = ToastProps & {\n dedupeKey?: string;\n duration?: number;\n};\n\ntype ToastItem = ToastOptions & { id: string; revision: number };\n\nconst ToastContext = createContext<((options: ToastOptions) => void) | null>(\n null,\n);\n\nexport const ToastProvider = ({ children }: PropsWithChildren) => {\n const [toasts, setToasts] = useState<ToastItem[]>([]);\n const dismissToast = useCallback(\n (id: string) =>\n setToasts((currentToasts) =>\n currentToasts.filter((toast) => toast.id !== id),\n ),\n [],\n );\n const showToast = useCallback(\n (options: ToastOptions) =>\n setToasts((currentToasts) => {\n const existingToastIndex = options.dedupeKey\n ? currentToasts.findIndex(\n (toast) => toast.dedupeKey === options.dedupeKey,\n )\n : -1;\n const nextToast =\n existingToastIndex < 0\n ? { ...options, id: crypto.randomUUID(), revision: 0 }\n : {\n ...currentToasts[existingToastIndex],\n ...options,\n revision: currentToasts[existingToastIndex].revision + 1,\n };\n\n return [\n nextToast,\n ...currentToasts.filter(\n (_, toastIndex) => toastIndex !== existingToastIndex,\n ),\n ].slice(0, 3);\n }),\n [],\n );\n\n return (\n <ToastContext.Provider value={showToast}>\n {children}\n <Viewport\n data-slot=\"toast-viewport\"\n $stackDepth={Math.max(0, toasts.length - 1)}\n >\n {toasts.map(\n ({ id, revision, dedupeKey, duration = 8000, ...props }, index) => (\n <StackItem\n key={`${dedupeKey ?? id}-${revision}`}\n data-pulse={revision > 0}\n $index={index}\n >\n <TimedToast\n {...props}\n id={id}\n duration={duration}\n dismiss={dismissToast}\n />\n </StackItem>\n ),\n )}\n </Viewport>\n </ToastContext.Provider>\n );\n};\n\nexport const useToast = () => {\n const showToast = useContext(ToastContext);\n if (!showToast) throw new Error(\"useToast must be used within ToastProvider\");\n return showToast;\n};\n\nconst TimedToast = ({\n id,\n duration,\n dismiss,\n action,\n onClose,\n onMouseEnter,\n onMouseLeave,\n onFocus,\n onBlur,\n ...props\n}: ToastOptions & { id: string; dismiss: (id: string) => void }) => {\n const [isTimerPaused, setIsTimerPaused] = useState(false);\n\n useEffect(() => {\n if (isTimerPaused) return;\n const timeoutId = window.setTimeout(() => dismiss(id), duration);\n return () => window.clearTimeout(timeoutId);\n }, [dismiss, duration, id, isTimerPaused]);\n\n return (\n <Toast\n {...props}\n onMouseEnter={(event) => {\n setIsTimerPaused(true);\n onMouseEnter?.(event);\n }}\n onMouseLeave={(event) => {\n if (!event.currentTarget.contains(document.activeElement))\n setIsTimerPaused(false);\n onMouseLeave?.(event);\n }}\n onFocus={(event) => {\n setIsTimerPaused(true);\n onFocus?.(event);\n }}\n onBlur={(event) => {\n if (\n !event.currentTarget.contains(event.relatedTarget) &&\n !event.currentTarget.matches(\":hover\")\n )\n setIsTimerPaused(false);\n onBlur?.(event);\n }}\n action={\n action && {\n label: action.label,\n onClick: () => {\n dismiss(id);\n action.onClick();\n },\n }\n }\n onClose={() => {\n onClose?.();\n dismiss(id);\n }}\n />\n );\n};\n\nconst ToastContainer = styled.div<{\n $border: Color.DesignColor;\n $background: Color.DesignColor;\n $compact: boolean;\n}>`\n display: flex;\n align-items: center;\n gap: ${Spacing.px16};\n\n box-sizing: border-box;\n width: ${({ $compact }) => ($compact ? \"300px\" : \"400px\")};\n max-width: calc(100vw - ${Spacing.px40});\n min-height: ${({ $compact }) => ($compact ? \"44px\" : \"64px\")};\n padding: ${Spacing.px12};\n\n background: ${({ $background }) => $background};\n border: 1px solid ${({ $border }) => $border};\n border-radius: ${Spacing.px8};\n`;\n\nconst ToastLayout = styled.div`\n display: flex;\n align-items: flex-start;\n gap: ${Spacing.px8};\n flex: 1;\n\n min-width: 0;\n`;\n\nconst ToastDetails = styled.div`\n display: flex;\n align-items: flex-start;\n gap: ${Spacing.px12};\n flex: 1;\n\n min-width: 0;\n`;\n\nconst ToastIconContainer = styled.span<{\n $background: Color.DesignColor;\n}>`\n display: grid;\n place-items: center;\n flex: 0 0 ${Spacing.px20};\n\n height: ${Spacing.px20};\n\n background: ${({ $background }) => $background};\n border-radius: ${Spacing.px4};\n`;\n\nconst ToastText = styled.div`\n display: flex;\n flex: 1;\n flex-direction: column;\n justify-content: center;\n\n min-width: 0;\n`;\n\nconst Title = styled.div`\n color: ${Color.typography.blackHigh};\n\n ${applyTypography(Typography.bodySecondaryBold)}\n`;\n\nconst Message = styled.div`\n color: ${Color.typography.blackMedium};\n\n ${applyTypography(Typography.bodySecondaryRegular)}\n`;\n\nconst ActionButton = styled.button`\n align-self: center;\n flex: none;\n\n box-sizing: border-box;\n height: ${Spacing.px40};\n padding: 10px 0;\n\n color: ${Color.typography.blackHigh};\n background: transparent;\n\n text-decoration: underline;\n white-space: nowrap;\n\n cursor: pointer;\n\n ${applyTypography(Typography.bodySecondaryMedium)}\n`;\n\nconst CloseButton = styled.button`\n display: grid;\n place-items: center;\n align-self: center;\n flex: 0 0 ${Spacing.px24};\n\n width: ${Spacing.px24};\n height: ${Spacing.px24};\n\n background: transparent;\n\n cursor: pointer;\n`;\n\nconst toastPulse = keyframes`\n 0%, 100% { transform: scale(1); }\n 50% { transform: scale(1.03); }\n`;\n\nconst StackItem = styled.div<{ $index: number }>`\n grid-area: 1 / 1;\n z-index: ${({ $index }) => 3 - $index};\n\n transform: translate(\n ${({ $index }) => `${-$index * 8}px`},\n ${({ $index }) => `${-$index * 8}px`}\n );\n transition: transform 250ms ease-in-out;\n pointer-events: ${({ $index }) => ($index === 0 ? \"auto\" : \"none\")};\n\n ${({ $index }) =>\n $index > 0 &&\n `\n > [role] > * {\n visibility: hidden;\n }\n `}\n\n &[data-pulse=\"true\"] > [role] {\n animation: ${toastPulse} 250ms ease-in-out;\n }\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n\n &[data-pulse=\"true\"] > [role] {\n animation: none;\n }\n }\n`;\n\nconst Viewport = styled.div<{ $stackDepth: number }>`\n display: grid;\n padding: ${({ $stackDepth }) =>\n `${$stackDepth * 8}px 0 0 ${$stackDepth * 8}px`};\n\n z-index: 100;\n position: fixed;\n top: 25px;\n right: ${Spacing.px20};\n transition: padding 250ms ease-in-out;\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n\n @media (width < ${Responsive.widthBreakpoint.laptop}) {\n top: auto;\n right: 50%;\n bottom: var(--toast-mobile-bottom-offset, ${Spacing.px88});\n transform: translateX(50%);\n }\n`;\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type HTMLAttributes, type PropsWithChildren, type ReactElement } from "react";
|
|
2
|
+
export type TooltipSide = "top" | "right" | "bottom" | "left";
|
|
3
|
+
export type TooltipProps = PropsWithChildren<{
|
|
4
|
+
open?: boolean;
|
|
5
|
+
defaultOpen?: boolean;
|
|
6
|
+
onOpenChange?: (open: boolean) => void;
|
|
7
|
+
}>;
|
|
8
|
+
declare const Tooltip: ({ children, open: isControlledOpen, defaultOpen, onOpenChange, }: TooltipProps) => import("react").JSX.Element;
|
|
9
|
+
export default Tooltip;
|
|
10
|
+
export type TooltipTriggerProps = {
|
|
11
|
+
children: ReactElement<{
|
|
12
|
+
"aria-describedby"?: string;
|
|
13
|
+
}>;
|
|
14
|
+
};
|
|
15
|
+
export declare const TooltipTrigger: ({ children }: TooltipTriggerProps) => import("react").JSX.Element;
|
|
16
|
+
export type TooltipContentProps = PropsWithChildren<Omit<HTMLAttributes<HTMLDivElement>, "content"> & {
|
|
17
|
+
side?: TooltipSide;
|
|
18
|
+
sideOffset?: number;
|
|
19
|
+
}>;
|
|
20
|
+
export declare const TooltipContent: ({ children, side, sideOffset, ...props }: TooltipContentProps) => import("react").ReactPortal | null;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cloneElement, createContext, useContext, useId, useLayoutEffect, useRef, useState, } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
import styled, { keyframes } from "styled-components";
|
|
6
|
+
import { Color, Spacing, Typography } from "../atoms";
|
|
7
|
+
import { applyTypography } from "../integrations";
|
|
8
|
+
const TooltipContext = createContext(null);
|
|
9
|
+
const useTooltip = () => {
|
|
10
|
+
const tooltipContext = useContext(TooltipContext);
|
|
11
|
+
if (!tooltipContext)
|
|
12
|
+
throw new Error("Tooltip components must be used within Tooltip");
|
|
13
|
+
return tooltipContext;
|
|
14
|
+
};
|
|
15
|
+
const Tooltip = ({ children, open: isControlledOpen, defaultOpen = false, onOpenChange, }) => {
|
|
16
|
+
const [isUncontrolledOpen, setIsUncontrolledOpen] = useState(defaultOpen);
|
|
17
|
+
const tooltipTriggerContainerRef = useRef(null);
|
|
18
|
+
const tooltipContentId = useId();
|
|
19
|
+
const isOpen = isControlledOpen ?? isUncontrolledOpen;
|
|
20
|
+
const setIsOpen = (nextIsOpen) => {
|
|
21
|
+
if (isControlledOpen === undefined)
|
|
22
|
+
setIsUncontrolledOpen(nextIsOpen);
|
|
23
|
+
onOpenChange?.(nextIsOpen);
|
|
24
|
+
};
|
|
25
|
+
return (_jsx(TooltipContext.Provider, { value: {
|
|
26
|
+
tooltipContentId,
|
|
27
|
+
isOpen,
|
|
28
|
+
setIsOpen,
|
|
29
|
+
tooltipTriggerContainerRef,
|
|
30
|
+
}, children: children }));
|
|
31
|
+
};
|
|
32
|
+
export default Tooltip;
|
|
33
|
+
export const TooltipTrigger = ({ children }) => {
|
|
34
|
+
const { tooltipContentId, isOpen, setIsOpen, tooltipTriggerContainerRef } = useTooltip();
|
|
35
|
+
return (_jsx(TooltipTriggerContainer, { ref: tooltipTriggerContainerRef, "data-slot": "tooltip-trigger", onMouseEnter: () => setIsOpen(true), onMouseLeave: () => {
|
|
36
|
+
if (!tooltipTriggerContainerRef.current?.contains(document.activeElement))
|
|
37
|
+
setIsOpen(false);
|
|
38
|
+
}, onFocus: () => setIsOpen(true), onBlur: () => {
|
|
39
|
+
if (!tooltipTriggerContainerRef.current?.matches(":hover"))
|
|
40
|
+
setIsOpen(false);
|
|
41
|
+
}, onKeyDown: (event) => event.key === "Escape" && setIsOpen(false), children: cloneElement(children, {
|
|
42
|
+
"aria-describedby": isOpen
|
|
43
|
+
? [children.props["aria-describedby"], tooltipContentId]
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.join(" ")
|
|
46
|
+
: children.props["aria-describedby"],
|
|
47
|
+
}) }));
|
|
48
|
+
};
|
|
49
|
+
const tooltipSides = ["top", "right", "bottom", "left"];
|
|
50
|
+
const oppositeTooltipSide = {
|
|
51
|
+
top: "bottom",
|
|
52
|
+
right: "left",
|
|
53
|
+
bottom: "top",
|
|
54
|
+
left: "right",
|
|
55
|
+
};
|
|
56
|
+
const clamp = (value, min, max) => Math.max(min, Math.min(value, max));
|
|
57
|
+
export const TooltipContent = ({ children, side = "top", sideOffset = 0, ...props }) => {
|
|
58
|
+
const { tooltipContentId, isOpen, tooltipTriggerContainerRef } = useTooltip();
|
|
59
|
+
const tooltipContentContainerRef = useRef(null);
|
|
60
|
+
const [tooltipPosition, setTooltipPosition] = useState();
|
|
61
|
+
useLayoutEffect(() => {
|
|
62
|
+
if (!isOpen)
|
|
63
|
+
return;
|
|
64
|
+
const updateTooltipPosition = () => {
|
|
65
|
+
const triggerContainerRect = tooltipTriggerContainerRef.current?.getBoundingClientRect();
|
|
66
|
+
const tooltipContentRect = tooltipContentContainerRef.current?.getBoundingClientRect();
|
|
67
|
+
if (!triggerContainerRect || !tooltipContentRect)
|
|
68
|
+
return;
|
|
69
|
+
const viewportPadding = 8;
|
|
70
|
+
const availableSpaceBySide = {
|
|
71
|
+
top: triggerContainerRect.top - viewportPadding,
|
|
72
|
+
right: window.innerWidth - triggerContainerRect.right - viewportPadding,
|
|
73
|
+
bottom: window.innerHeight - triggerContainerRect.bottom - viewportPadding,
|
|
74
|
+
left: triggerContainerRect.left - viewportPadding,
|
|
75
|
+
};
|
|
76
|
+
const oppositeSide = oppositeTooltipSide[side];
|
|
77
|
+
const resolvedSide = [
|
|
78
|
+
side,
|
|
79
|
+
oppositeSide,
|
|
80
|
+
...tooltipSides.filter((candidateSide) => ![side, oppositeSide].includes(candidateSide)),
|
|
81
|
+
].find((candidateSide) => availableSpaceBySide[candidateSide] >=
|
|
82
|
+
(candidateSide === "top" || candidateSide === "bottom"
|
|
83
|
+
? tooltipContentRect.height
|
|
84
|
+
: tooltipContentRect.width) +
|
|
85
|
+
sideOffset) ?? side;
|
|
86
|
+
let tooltipTop = triggerContainerRect.top +
|
|
87
|
+
(triggerContainerRect.height - tooltipContentRect.height) / 2;
|
|
88
|
+
let tooltipLeft = triggerContainerRect.left +
|
|
89
|
+
(triggerContainerRect.width - tooltipContentRect.width) / 2;
|
|
90
|
+
if (resolvedSide === "top")
|
|
91
|
+
tooltipTop =
|
|
92
|
+
triggerContainerRect.top - tooltipContentRect.height - sideOffset;
|
|
93
|
+
if (resolvedSide === "right")
|
|
94
|
+
tooltipLeft = triggerContainerRect.right + sideOffset;
|
|
95
|
+
if (resolvedSide === "bottom")
|
|
96
|
+
tooltipTop = triggerContainerRect.bottom + sideOffset;
|
|
97
|
+
if (resolvedSide === "left")
|
|
98
|
+
tooltipLeft =
|
|
99
|
+
triggerContainerRect.left - tooltipContentRect.width - sideOffset;
|
|
100
|
+
tooltipTop = clamp(tooltipTop, viewportPadding, window.innerHeight - tooltipContentRect.height - viewportPadding);
|
|
101
|
+
tooltipLeft = clamp(tooltipLeft, viewportPadding, window.innerWidth - tooltipContentRect.width - viewportPadding);
|
|
102
|
+
setTooltipPosition({
|
|
103
|
+
top: tooltipTop,
|
|
104
|
+
left: tooltipLeft,
|
|
105
|
+
side: resolvedSide,
|
|
106
|
+
arrowOffset: resolvedSide === "top" || resolvedSide === "bottom"
|
|
107
|
+
? clamp(triggerContainerRect.left +
|
|
108
|
+
triggerContainerRect.width / 2 -
|
|
109
|
+
tooltipLeft, 8, tooltipContentRect.width - 8)
|
|
110
|
+
: clamp(triggerContainerRect.top +
|
|
111
|
+
triggerContainerRect.height / 2 -
|
|
112
|
+
tooltipTop, 8, tooltipContentRect.height - 8),
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
updateTooltipPosition();
|
|
116
|
+
window.addEventListener("resize", updateTooltipPosition);
|
|
117
|
+
window.addEventListener("scroll", updateTooltipPosition, true);
|
|
118
|
+
return () => {
|
|
119
|
+
window.removeEventListener("resize", updateTooltipPosition);
|
|
120
|
+
window.removeEventListener("scroll", updateTooltipPosition, true);
|
|
121
|
+
};
|
|
122
|
+
}, [children, isOpen, side, sideOffset, tooltipTriggerContainerRef]);
|
|
123
|
+
if (!isOpen || typeof document === "undefined")
|
|
124
|
+
return null;
|
|
125
|
+
return createPortal(_jsxs(TooltipContentContainer, { ...props, ref: tooltipContentContainerRef, id: tooltipContentId, role: "tooltip", "data-slot": "tooltip-content", "data-side": tooltipPosition?.side ?? side, "$visible": !!tooltipPosition, style: {
|
|
126
|
+
...props.style,
|
|
127
|
+
top: tooltipPosition?.top ?? 0,
|
|
128
|
+
left: tooltipPosition?.left ?? 0,
|
|
129
|
+
}, children: [children, tooltipPosition && (_jsx(TooltipArrow, { "aria-hidden": true, "data-slot": "tooltip-arrow", "$side": tooltipPosition.side, "$offset": tooltipPosition.arrowOffset }))] }), document.body);
|
|
130
|
+
};
|
|
131
|
+
const tooltipFadeIn = keyframes `
|
|
132
|
+
from { opacity: 0; }
|
|
133
|
+
to { opacity: 1; }
|
|
134
|
+
`;
|
|
135
|
+
const TooltipTriggerContainer = styled.span `
|
|
136
|
+
display: inline-flex;
|
|
137
|
+
`;
|
|
138
|
+
const TooltipContentContainer = styled.div `
|
|
139
|
+
position: fixed;
|
|
140
|
+
z-index: 100;
|
|
141
|
+
|
|
142
|
+
display: flex;
|
|
143
|
+
flex-direction: column;
|
|
144
|
+
align-items: stretch;
|
|
145
|
+
box-sizing: border-box;
|
|
146
|
+
width: max-content;
|
|
147
|
+
max-width: min(320px, calc(100vw - ${Spacing.px16}));
|
|
148
|
+
padding: ${Spacing.px12};
|
|
149
|
+
|
|
150
|
+
color: ${Color.typography.whiteHigh};
|
|
151
|
+
background: ${Color.neutral.black};
|
|
152
|
+
border-radius: ${Spacing.px8};
|
|
153
|
+
|
|
154
|
+
text-align: center;
|
|
155
|
+
visibility: ${({ $visible }) => ($visible ? "visible" : "hidden")};
|
|
156
|
+
pointer-events: none;
|
|
157
|
+
animation: ${tooltipFadeIn} 120ms ease-out;
|
|
158
|
+
|
|
159
|
+
${applyTypography(Typography.captionRegular)}
|
|
160
|
+
|
|
161
|
+
@media (prefers-reduced-motion: reduce) {
|
|
162
|
+
animation: none;
|
|
163
|
+
}
|
|
164
|
+
`;
|
|
165
|
+
const TooltipArrow = styled.span `
|
|
166
|
+
position: absolute;
|
|
167
|
+
${({ $side, $offset }) => {
|
|
168
|
+
if ($side === "top")
|
|
169
|
+
return `bottom: -${Spacing.px4}; left: ${$offset}px; transform: translateX(-50%) rotate(45deg);`;
|
|
170
|
+
if ($side === "right")
|
|
171
|
+
return `left: -${Spacing.px4}; top: ${$offset}px; transform: translateY(-50%) rotate(45deg);`;
|
|
172
|
+
if ($side === "bottom")
|
|
173
|
+
return `top: -${Spacing.px4}; left: ${$offset}px; transform: translateX(-50%) rotate(45deg);`;
|
|
174
|
+
return `right: -${Spacing.px4}; top: ${$offset}px; transform: translateY(-50%) rotate(45deg);`;
|
|
175
|
+
}}
|
|
176
|
+
|
|
177
|
+
width: ${Spacing.px8};
|
|
178
|
+
height: ${Spacing.px8};
|
|
179
|
+
|
|
180
|
+
background: ${Color.neutral.black};
|
|
181
|
+
`;
|
|
182
|
+
//# sourceMappingURL=Tooltip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tooltip.js","sourceRoot":"","sources":["../../../core/molecules/Tooltip.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport {\n cloneElement,\n createContext,\n type HTMLAttributes,\n type PropsWithChildren,\n type ReactElement,\n type RefObject,\n useContext,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styled, { keyframes } from \"styled-components\";\nimport { Color, Spacing, Typography } from \"../atoms\";\nimport { applyTypography } from \"../integrations\";\n\nexport type TooltipSide = \"top\" | \"right\" | \"bottom\" | \"left\";\n\ntype TooltipContextValue = {\n tooltipContentId: string;\n isOpen: boolean;\n setIsOpen: (isOpen: boolean) => void;\n tooltipTriggerContainerRef: RefObject<HTMLSpanElement | null>;\n};\n\nconst TooltipContext = createContext<TooltipContextValue | null>(null);\n\nconst useTooltip = () => {\n const tooltipContext = useContext(TooltipContext);\n if (!tooltipContext)\n throw new Error(\"Tooltip components must be used within Tooltip\");\n return tooltipContext;\n};\n\nexport type TooltipProps = PropsWithChildren<{\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n}>;\n\nconst Tooltip = ({\n children,\n open: isControlledOpen,\n defaultOpen = false,\n onOpenChange,\n}: TooltipProps) => {\n const [isUncontrolledOpen, setIsUncontrolledOpen] = useState(defaultOpen);\n const tooltipTriggerContainerRef = useRef<HTMLSpanElement>(null);\n const tooltipContentId = useId();\n const isOpen = isControlledOpen ?? isUncontrolledOpen;\n\n const setIsOpen = (nextIsOpen: boolean) => {\n if (isControlledOpen === undefined) setIsUncontrolledOpen(nextIsOpen);\n onOpenChange?.(nextIsOpen);\n };\n\n return (\n <TooltipContext.Provider\n value={{\n tooltipContentId,\n isOpen,\n setIsOpen,\n tooltipTriggerContainerRef,\n }}\n >\n {children}\n </TooltipContext.Provider>\n );\n};\n\nexport default Tooltip;\n\nexport type TooltipTriggerProps = {\n children: ReactElement<{ \"aria-describedby\"?: string }>;\n};\n\nexport const TooltipTrigger = ({ children }: TooltipTriggerProps) => {\n const { tooltipContentId, isOpen, setIsOpen, tooltipTriggerContainerRef } =\n useTooltip();\n\n return (\n <TooltipTriggerContainer\n ref={tooltipTriggerContainerRef}\n data-slot=\"tooltip-trigger\"\n onMouseEnter={() => setIsOpen(true)}\n onMouseLeave={() => {\n if (\n !tooltipTriggerContainerRef.current?.contains(document.activeElement)\n )\n setIsOpen(false);\n }}\n onFocus={() => setIsOpen(true)}\n onBlur={() => {\n if (!tooltipTriggerContainerRef.current?.matches(\":hover\"))\n setIsOpen(false);\n }}\n onKeyDown={(event) => event.key === \"Escape\" && setIsOpen(false)}\n >\n {cloneElement(children, {\n \"aria-describedby\": isOpen\n ? [children.props[\"aria-describedby\"], tooltipContentId]\n .filter(Boolean)\n .join(\" \")\n : children.props[\"aria-describedby\"],\n })}\n </TooltipTriggerContainer>\n );\n};\n\nexport type TooltipContentProps = PropsWithChildren<\n Omit<HTMLAttributes<HTMLDivElement>, \"content\"> & {\n side?: TooltipSide;\n sideOffset?: number;\n }\n>;\n\ntype TooltipPosition = {\n top: number;\n left: number;\n arrowOffset: number;\n side: TooltipSide;\n};\n\nconst tooltipSides: TooltipSide[] = [\"top\", \"right\", \"bottom\", \"left\"];\nconst oppositeTooltipSide: Record<TooltipSide, TooltipSide> = {\n top: \"bottom\",\n right: \"left\",\n bottom: \"top\",\n left: \"right\",\n};\nconst clamp = (value: number, min: number, max: number) =>\n Math.max(min, Math.min(value, max));\n\nexport const TooltipContent = ({\n children,\n side = \"top\",\n sideOffset = 0,\n ...props\n}: TooltipContentProps) => {\n const { tooltipContentId, isOpen, tooltipTriggerContainerRef } = useTooltip();\n const tooltipContentContainerRef = useRef<HTMLDivElement>(null);\n const [tooltipPosition, setTooltipPosition] = useState<TooltipPosition>();\n\n useLayoutEffect(() => {\n if (!isOpen) return;\n\n const updateTooltipPosition = () => {\n const triggerContainerRect =\n tooltipTriggerContainerRef.current?.getBoundingClientRect();\n const tooltipContentRect =\n tooltipContentContainerRef.current?.getBoundingClientRect();\n if (!triggerContainerRect || !tooltipContentRect) return;\n\n const viewportPadding = 8;\n const availableSpaceBySide: Record<TooltipSide, number> = {\n top: triggerContainerRect.top - viewportPadding,\n right: window.innerWidth - triggerContainerRect.right - viewportPadding,\n bottom:\n window.innerHeight - triggerContainerRect.bottom - viewportPadding,\n left: triggerContainerRect.left - viewportPadding,\n };\n const oppositeSide = oppositeTooltipSide[side];\n const resolvedSide =\n [\n side,\n oppositeSide,\n ...tooltipSides.filter(\n (candidateSide) => ![side, oppositeSide].includes(candidateSide),\n ),\n ].find(\n (candidateSide) =>\n availableSpaceBySide[candidateSide] >=\n (candidateSide === \"top\" || candidateSide === \"bottom\"\n ? tooltipContentRect.height\n : tooltipContentRect.width) +\n sideOffset,\n ) ?? side;\n\n let tooltipTop =\n triggerContainerRect.top +\n (triggerContainerRect.height - tooltipContentRect.height) / 2;\n let tooltipLeft =\n triggerContainerRect.left +\n (triggerContainerRect.width - tooltipContentRect.width) / 2;\n if (resolvedSide === \"top\")\n tooltipTop =\n triggerContainerRect.top - tooltipContentRect.height - sideOffset;\n if (resolvedSide === \"right\")\n tooltipLeft = triggerContainerRect.right + sideOffset;\n if (resolvedSide === \"bottom\")\n tooltipTop = triggerContainerRect.bottom + sideOffset;\n if (resolvedSide === \"left\")\n tooltipLeft =\n triggerContainerRect.left - tooltipContentRect.width - sideOffset;\n\n tooltipTop = clamp(\n tooltipTop,\n viewportPadding,\n window.innerHeight - tooltipContentRect.height - viewportPadding,\n );\n tooltipLeft = clamp(\n tooltipLeft,\n viewportPadding,\n window.innerWidth - tooltipContentRect.width - viewportPadding,\n );\n\n setTooltipPosition({\n top: tooltipTop,\n left: tooltipLeft,\n side: resolvedSide,\n arrowOffset:\n resolvedSide === \"top\" || resolvedSide === \"bottom\"\n ? clamp(\n triggerContainerRect.left +\n triggerContainerRect.width / 2 -\n tooltipLeft,\n 8,\n tooltipContentRect.width - 8,\n )\n : clamp(\n triggerContainerRect.top +\n triggerContainerRect.height / 2 -\n tooltipTop,\n 8,\n tooltipContentRect.height - 8,\n ),\n });\n };\n\n updateTooltipPosition();\n window.addEventListener(\"resize\", updateTooltipPosition);\n window.addEventListener(\"scroll\", updateTooltipPosition, true);\n return () => {\n window.removeEventListener(\"resize\", updateTooltipPosition);\n window.removeEventListener(\"scroll\", updateTooltipPosition, true);\n };\n }, [children, isOpen, side, sideOffset, tooltipTriggerContainerRef]);\n\n if (!isOpen || typeof document === \"undefined\") return null;\n\n return createPortal(\n <TooltipContentContainer\n {...props}\n ref={tooltipContentContainerRef}\n id={tooltipContentId}\n role=\"tooltip\"\n data-slot=\"tooltip-content\"\n data-side={tooltipPosition?.side ?? side}\n $visible={!!tooltipPosition}\n style={{\n ...props.style,\n top: tooltipPosition?.top ?? 0,\n left: tooltipPosition?.left ?? 0,\n }}\n >\n {children}\n {tooltipPosition && (\n <TooltipArrow\n aria-hidden\n data-slot=\"tooltip-arrow\"\n $side={tooltipPosition.side}\n $offset={tooltipPosition.arrowOffset}\n />\n )}\n </TooltipContentContainer>,\n document.body,\n );\n};\n\nconst tooltipFadeIn = keyframes`\n from { opacity: 0; }\n to { opacity: 1; }\n`;\n\nconst TooltipTriggerContainer = styled.span`\n display: inline-flex;\n`;\n\nconst TooltipContentContainer = styled.div<{ $visible: boolean }>`\n position: fixed;\n z-index: 100;\n\n display: flex;\n flex-direction: column;\n align-items: stretch;\n box-sizing: border-box;\n width: max-content;\n max-width: min(320px, calc(100vw - ${Spacing.px16}));\n padding: ${Spacing.px12};\n\n color: ${Color.typography.whiteHigh};\n background: ${Color.neutral.black};\n border-radius: ${Spacing.px8};\n\n text-align: center;\n visibility: ${({ $visible }) => ($visible ? \"visible\" : \"hidden\")};\n pointer-events: none;\n animation: ${tooltipFadeIn} 120ms ease-out;\n\n ${applyTypography(Typography.captionRegular)}\n\n @media (prefers-reduced-motion: reduce) {\n animation: none;\n }\n`;\n\nconst TooltipArrow = styled.span<{ $side: TooltipSide; $offset: number }>`\n position: absolute;\n ${({ $side, $offset }) => {\n if ($side === \"top\")\n return `bottom: -${Spacing.px4}; left: ${$offset}px; transform: translateX(-50%) rotate(45deg);`;\n if ($side === \"right\")\n return `left: -${Spacing.px4}; top: ${$offset}px; transform: translateY(-50%) rotate(45deg);`;\n if ($side === \"bottom\")\n return `top: -${Spacing.px4}; left: ${$offset}px; transform: translateX(-50%) rotate(45deg);`;\n return `right: -${Spacing.px4}; top: ${$offset}px; transform: translateY(-50%) rotate(45deg);`;\n }}\n\n width: ${Spacing.px8};\n height: ${Spacing.px8};\n\n background: ${Color.neutral.black};\n`;\n"]}
|
|
@@ -6,3 +6,5 @@ export { default as Overlay } from "./Overlay";
|
|
|
6
6
|
export { default as PageLoader } from "./PageLoader";
|
|
7
7
|
export { default as RadioButton } from "./RadioButton";
|
|
8
8
|
export { default as Textarea } from "./Textarea";
|
|
9
|
+
export { default as Tooltip, TooltipContent, TooltipTrigger, type TooltipContentProps, type TooltipProps, type TooltipSide, type TooltipTriggerProps, } from "./Tooltip";
|
|
10
|
+
export { default as Toast, ToastProvider, useToast, type ToastOptions, type ToastProps, type ToastVariant, } from "./Toast";
|
package/core/molecules/index.js
CHANGED
|
@@ -6,4 +6,6 @@ export { default as Overlay } from "./Overlay";
|
|
|
6
6
|
export { default as PageLoader } from "./PageLoader";
|
|
7
7
|
export { default as RadioButton } from "./RadioButton";
|
|
8
8
|
export { default as Textarea } from "./Textarea";
|
|
9
|
+
export { default as Tooltip, TooltipContent, TooltipTrigger, } from "./Tooltip";
|
|
10
|
+
export { default as Toast, ToastProvider, useToast, } from "./Toast";
|
|
9
11
|
//# sourceMappingURL=index.js.map
|