fis-component 0.0.79 → 0.0.81

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.
@@ -2,7 +2,7 @@ import React from "react";
2
2
  export type ThemeType = "neutral" | "info" | "positive" | "caution" | "negative";
3
3
  export type ToastType = "link" | "button" | "no-action";
4
4
  export type PositionType = "inline" | "bottom";
5
- export type ToastPositionType = "top-center" | "bottom-center";
5
+ export type ToastPositionType = "top-center" | "bottom-center" | "top-right" | "bottom-right" | "top-left" | "bottom-left";
6
6
  export type ToastProps = {
7
7
  className?: string;
8
8
  theme?: ThemeType;
@@ -10,6 +10,6 @@ export declare const PTitleSC: import("styled-components/dist/types").IStyledCom
10
10
  export declare const LinkSC: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, never>> & string;
11
11
  export declare const DivTitleWrap: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, ToastProps>> & string;
12
12
  export declare const ToastWrapper: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
13
- $position?: "top-center" | "bottom-center";
13
+ $position?: "top-center" | "bottom-center" | "top-right" | "bottom-right" | "top-left" | "bottom-left";
14
14
  }>> & string;
15
15
  export {};
@@ -23,6 +23,8 @@ interface TooltipProps extends Omit<TooltipPropsAntd, "variant" | "title"> {
23
23
  size?: TooltipSize;
24
24
  /** Child elements that the tooltip wraps (optional) */
25
25
  children?: React.ReactNode;
26
+ /** Enable smart placement (auto left/right) */
27
+ smartPlacement?: boolean;
26
28
  }
27
29
  declare const FISTooltip: FC<TooltipProps>;
28
30
  export default FISTooltip;
@@ -0,0 +1,5 @@
1
+ export type TooltipPlacement = "left" | "right";
2
+ export declare const useSmartTooltipPlacement: () => {
3
+ placement: TooltipPlacement;
4
+ calculatePlacement: (triggerEl: HTMLElement | null) => void;
5
+ };
package/dist/esm/index.js CHANGED
@@ -61815,13 +61815,139 @@ const DivTitleWrap = styled.div `
61815
61815
  `;
61816
61816
  const ToastWrapper = styled.div `
61817
61817
  position: fixed;
61818
- ${(props) => props.$position === "bottom-center" ? "bottom: 20px;" : "top: 20px;"}
61819
- left: 50%;
61820
- transform: translateX(-50%);
61821
- z-index: 1000;
61818
+ z-index: 1050; /* Cao hơn bootstrap modal (1040) các component khác */
61822
61819
  display: flex;
61823
61820
  flex-direction: column;
61824
- gap: 8px;
61821
+ gap: 12px; /* Tăng gap để toast không quá sát nhau */
61822
+ max-width: 420px; /* Tăng một chút cho nội dung dài */
61823
+ min-width: 320px; /* Tăng min-width cho đẹp hơn */
61824
+ pointer-events: none; /* Tránh chặn click vào các element phía sau */
61825
+
61826
+ /* Cho phép click vào các toast con */
61827
+ > * {
61828
+ pointer-events: auto;
61829
+ }
61830
+
61831
+ ${(props) => {
61832
+ switch (props.$position) {
61833
+ case "top-center":
61834
+ return css `
61835
+ top: 24px;
61836
+ left: 50%;
61837
+ transform: translateX(-50%);
61838
+ `;
61839
+ case "bottom-center":
61840
+ return css `
61841
+ bottom: 24px;
61842
+ left: 50%;
61843
+ transform: translateX(-50%);
61844
+ flex-direction: column-reverse; /* Toast mới xuất hiện từ dưới lên */
61845
+ `;
61846
+ case "top-right":
61847
+ return css `
61848
+ top: 24px;
61849
+ right: 24px;
61850
+ `;
61851
+ case "bottom-right":
61852
+ return css `
61853
+ bottom: 24px;
61854
+ right: 24px;
61855
+ flex-direction: column-reverse;
61856
+ `;
61857
+ case "top-left":
61858
+ return css `
61859
+ top: 24px;
61860
+ left: 24px;
61861
+ `;
61862
+ case "bottom-left":
61863
+ return css `
61864
+ bottom: 24px;
61865
+ left: 24px;
61866
+ flex-direction: column-reverse;
61867
+ `;
61868
+ default:
61869
+ return css `
61870
+ top: 24px;
61871
+ left: 50%;
61872
+ transform: translateX(-50%);
61873
+ `;
61874
+ }
61875
+ }}
61876
+
61877
+ /* Animation cho toast xuất hiện */
61878
+ > div {
61879
+ animation: toastSlideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
61880
+ }
61881
+
61882
+ @keyframes toastSlideIn {
61883
+ from {
61884
+ opacity: 0;
61885
+ transform: translateY(-12px) scale(0.95);
61886
+ }
61887
+ to {
61888
+ opacity: 1;
61889
+ transform: translateY(0) scale(1);
61890
+ }
61891
+ }
61892
+
61893
+ /* Animation cho bottom positions */
61894
+ ${(props) => props.$position?.includes("bottom") &&
61895
+ css `
61896
+ > div {
61897
+ animation: toastSlideInBottom 0.3s cubic-bezier(0.4, 0, 0.2, 1);
61898
+ }
61899
+
61900
+ @keyframes toastSlideInBottom {
61901
+ from {
61902
+ opacity: 0;
61903
+ transform: translateY(12px) scale(0.95);
61904
+ }
61905
+ to {
61906
+ opacity: 1;
61907
+ transform: translateY(0) scale(1);
61908
+ }
61909
+ }
61910
+ `}
61911
+
61912
+ /* Responsive cho mobile */
61913
+ @media (max-width: 768px) {
61914
+ max-width: calc(100vw - 32px);
61915
+ min-width: calc(100vw - 32px);
61916
+
61917
+ ${(props) => {
61918
+ if (props.$position?.includes("center")) {
61919
+ return css `
61920
+ left: 16px;
61921
+ right: 16px;
61922
+ transform: none;
61923
+ `;
61924
+ }
61925
+ else if (props.$position?.includes("left")) {
61926
+ return css `
61927
+ left: 16px;
61928
+ `;
61929
+ }
61930
+ else if (props.$position?.includes("right")) {
61931
+ return css `
61932
+ right: 16px;
61933
+ `;
61934
+ }
61935
+ }}
61936
+
61937
+ ${(props) => props.$position?.includes("top")
61938
+ ? css `
61939
+ top: 16px;
61940
+ `
61941
+ : css `
61942
+ bottom: 16px;
61943
+ `}
61944
+ }
61945
+
61946
+ /* Tablet breakpoint */
61947
+ @media (max-width: 1024px) and (min-width: 769px) {
61948
+ max-width: 380px;
61949
+ min-width: 300px;
61950
+ }
61825
61951
  `;
61826
61952
 
61827
61953
  const getPadding = (size, borderPath) => {
@@ -62547,7 +62673,7 @@ const ToastProvider = ({ children, }) => {
62547
62673
  setToasts((prev) => prev.slice(1));
62548
62674
  }, duration);
62549
62675
  }, []);
62550
- return (jsxs(ToastContext.Provider, { value: { showToast }, children: [children, toasts.length > 0 && (jsx(ToastWrapper, { "$position": toasts[0].position || "top-center", children: toasts.map((toast, index) => (jsx(FISToast, { ...toast }, index))) }))] }));
62676
+ return (jsxs(ToastContext.Provider, { value: { showToast }, children: [children, toasts.length > 0 && (jsx(ToastWrapper, { "$position": toasts[0].position || "top-right", children: toasts.map((toast, index) => (jsx(FISToast, { ...toast }, index))) }))] }));
62551
62677
  };
62552
62678
 
62553
62679
  const FISThemeProvider = ({ theme: theme$1 = theme, children, }) => (jsxs(ThemeProvider, { theme: theme$1, children: [jsx(GlobalStyle, { theme: theme$1 }), jsx(NotificationProvider, { children: jsx(ToastProvider, { children: children }) })] }));
@@ -66297,18 +66423,58 @@ const DivTooltipSC = styled.div `
66297
66423
  }
66298
66424
  `;
66299
66425
 
66300
- const FISTooltip = ({ className, variant = "primary", size = "md", title: titleProp, children, ...rest }) => {
66426
+ const useSmartTooltipPlacement = () => {
66427
+ const [placement, setPlacement] = useState("right");
66428
+ const calculatePlacement = useCallback((triggerEl) => {
66429
+ if (!triggerEl)
66430
+ return;
66431
+ const rect = triggerEl.getBoundingClientRect();
66432
+ const viewportWidth = window.innerWidth;
66433
+ const leftSpace = rect.left;
66434
+ const rightSpace = viewportWidth - rect.right;
66435
+ if (rightSpace > leftSpace) {
66436
+ setPlacement("right");
66437
+ }
66438
+ else if (leftSpace > rightSpace) {
66439
+ setPlacement("left");
66440
+ }
66441
+ else {
66442
+ setPlacement("right");
66443
+ }
66444
+ }, []);
66445
+ return { placement, calculatePlacement };
66446
+ };
66447
+
66448
+ const FISTooltip = ({ className, variant = "primary", size = "md", title: titleProp, children, smartPlacement = false, ...rest }) => {
66301
66449
  const theme = useTheme$1();
66450
+ const triggerRef = useRef(null);
66451
+ const { placement, calculatePlacement } = useSmartTooltipPlacement();
66452
+ useEffect(() => {
66453
+ if (!smartPlacement)
66454
+ return;
66455
+ const el = triggerRef.current;
66456
+ if (!el)
66457
+ return;
66458
+ const handleMouseEnter = () => calculatePlacement(el);
66459
+ el.addEventListener("mouseenter", handleMouseEnter);
66460
+ calculatePlacement(el);
66461
+ return () => {
66462
+ el.removeEventListener("mouseenter", handleMouseEnter);
66463
+ };
66464
+ }, [smartPlacement, calculatePlacement]);
66465
+ const tooltipPlacement = smartPlacement
66466
+ ? placement
66467
+ : rest.placement || "right";
66302
66468
  if (isTooltipTitleCustom(titleProp)) {
66303
66469
  // Create properly structured custom title
66304
66470
  const title = (jsxs(DivTooltipContentWrapperSC, { children: [jsxs(DivTooltipContentSC, { children: [jsx(DivTooltipLabelSC, { children: titleProp.label }), titleProp.description && (jsx(DivTooltipDescriptionSC, { children: titleProp.description }))] }), titleProp.link && (jsx(ATooltipLinkSC, { href: titleProp.linkUrl, target: "_blank", rel: "noopener noreferrer", children: titleProp.link }))] }));
66305
- return (jsx(DivTooltipSC, { className: className, "$size": size, "$variant": variant, children: jsx(Tooltip, { color: variant === "primary"
66306
- ? theme["com/tooltip/size-md/theme-black/background-color"]
66307
- : theme["com/tooltip/size-md/theme-white/background-color"], title: title, getTooltipContainer: (trigger) => trigger, ...rest, children: children || jsx(Button$2, { children: "RT" }) }) }));
66471
+ return (jsx(DivTooltipSC, { className: className, "$size": size, "$variant": variant, children: jsx("span", { ref: smartPlacement ? triggerRef : undefined, children: jsx(Tooltip, { color: variant === "primary"
66472
+ ? theme["com/tooltip/size-md/theme-black/background-color"]
66473
+ : theme["com/tooltip/size-md/theme-white/background-color"], title: title, getTooltipContainer: () => document.body, placement: tooltipPlacement, ...rest, children: children || jsx(Button$2, { children: "RT" }) }) }) }));
66308
66474
  }
66309
- return (jsx(DivTooltipSC, { className: className, "$size": size, "$variant": variant, children: jsx(Tooltip, { color: variant === "primary"
66310
- ? theme["com/tooltip/size-md/theme-black/background-color"]
66311
- : theme["com/tooltip/size-md/theme-white/background-color"], title: titleProp, getTooltipContainer: (trigger) => trigger, ...rest, children: children || jsx(Button$2, { children: "RT" }) }) }));
66475
+ return (jsx(DivTooltipSC, { className: className, "$size": size, "$variant": variant, children: jsx("span", { ref: smartPlacement ? triggerRef : undefined, children: jsx(Tooltip, { color: variant === "primary"
66476
+ ? theme["com/tooltip/size-md/theme-black/background-color"]
66477
+ : theme["com/tooltip/size-md/theme-white/background-color"], title: titleProp, getTooltipContainer: () => document.body, placement: tooltipPlacement, ...rest, children: children || jsx(Button$2, { children: "RT" }) }) }) }));
66312
66478
  };
66313
66479
  FISTooltip.displayName = "FISTooltip";
66314
66480
 
@@ -67329,6 +67495,14 @@ const FISBadge = ({ className, icon, label, size = "sm", color, status, type = "
67329
67495
  FISBadge.displayName = "FISBadge";
67330
67496
 
67331
67497
  const FISMenuItem = ({ title, description, iconPrefix, type = "action", size = "md", selected = false, negative = false, disable, onClickMenu, }) => {
67498
+ const titleRef = useRef(null);
67499
+ const [isTruncated, setIsTruncated] = useState(false);
67500
+ useEffect(() => {
67501
+ const el = titleRef.current;
67502
+ if (el) {
67503
+ setIsTruncated(el.scrollWidth > el.clientWidth);
67504
+ }
67505
+ }, [title]);
67332
67506
  const renderSuffixIcon = () => {
67333
67507
  switch (type) {
67334
67508
  case "action":
@@ -67345,9 +67519,12 @@ const FISMenuItem = ({ title, description, iconPrefix, type = "action", size = "
67345
67519
  negative: negative,
67346
67520
  }), "$size": size, "$selected": selected, "$type": type, "$disable": disable, onClick: onClickMenu, children: [iconPrefix && (jsx(DivPrefixIconSC, { className: classNames({
67347
67521
  negative: negative,
67348
- }), "$size": size, "$type": type, "$selected": selected, "$disable": disable, children: iconPrefix })), jsxs(DivContentSC$2, { children: [title && (jsx(PTitleSC$1, { className: classNames({
67349
- negative: negative,
67350
- }), "$selected": selected, "$type": type, "$disable": disable, children: title })), description && (jsx(PDescriptionSC, { className: classNames({
67522
+ }), "$size": size, "$type": type, "$selected": selected, "$disable": disable, children: iconPrefix })), jsxs(DivContentSC$2, { children: [title &&
67523
+ (isTruncated ? (jsx(FISTooltip, { title: title, variant: "primary", smartPlacement: true, children: jsx(PTitleSC$1, { ref: titleRef, className: classNames({
67524
+ negative: negative,
67525
+ }), "$selected": selected, "$type": type, "$disable": disable, children: title }) })) : (jsx(PTitleSC$1, { ref: titleRef, className: classNames({
67526
+ negative: negative,
67527
+ }), "$selected": selected, "$type": type, "$disable": disable, children: title }))), description && (jsx(PDescriptionSC, { className: classNames({
67351
67528
  negative: negative,
67352
67529
  }), "$type": type, "$selected": selected, "$disable": disable, children: description }))] }), renderSuffixIcon()] }));
67353
67530
  };
@@ -67477,7 +67654,7 @@ const FISMenuSelect = ({ placeholder, groups, size = "md", multi = false, select
67477
67654
  onClickMenu?.();
67478
67655
  }
67479
67656
  };
67480
- return (jsxs(DivContainerSC$4, { "$maxHeight": maxHeight, className: className, children: [showInput && !combobox && (jsx(DivSearchSC, { children: jsx(FISInputText, { iconPrefix: jsx(SearchIcon, {}), placeholder: placeholder, value: search, onChange: handleSearchChange }) })), (shouldShowNoResult || noResult) && (jsxs(DivLoaderSC, { children: [jsx(DivIconDataSC, { children: jsx(NoResultIcon, {}) }), jsx(PTitleSC$2, { children: noResultText })] })), noData && (jsxs(DivLoaderSC, { children: [jsx(DivIconDataSC, { children: jsx(NoDataIcon, {}) }), jsx(PTitleSC$2, { children: noDataText })] })), loading && (jsxs(DivLoaderSC, { children: [jsx(FISProgressCircular, { size: size, variant: "indeterminate" }), jsx(PTitleSC$2, { children: loadingText })] })), !shouldShowNoResult && !noData && !loading && (jsxs(Fragment, { children: [jsx(MenuContent, { "$removeSelectedGroup": !!removeSelectedGroup, children: groupsToRender.map((group, index) => (jsxs(DivWrapperSC$4, { children: [index !== 0 && jsx(FISMenuSection, { withDivider: true }), group?.groupLabel && (jsx(FISMenuSection, { label: group?.groupLabel })), group.items.map((item, idx) => (jsx(FISMenuItem, { title: item.label, description: item.description, size: size, onClickMenu: () => handleItemClick(item), selected: selectedValues.includes(item.value), type: "select" }, idx)))] }, index))) }), removeSelectedGroup && (jsx(FixedBottomSection, { children: jsx(DivWrapperSC$4, { children: removeSelectedGroup.items.map((item, idx) => (jsx(FISMenuItem, { title: item.label, description: item.description, size: size, onClickMenu: () => onChangeSelected?.([]), type: "select", iconPrefix: jsx(RemoveIcon, {}), negative: true }, idx))) }) }))] }))] }));
67657
+ return (jsxs(DivContainerSC$4, { "$maxHeight": maxHeight, className: className, children: [showInput && !combobox && (jsx(DivSearchSC, { children: jsx(FISInputText, { iconPrefix: jsx(SearchIcon, {}), placeholder: placeholder, value: search, onChange: handleSearchChange }) })), (shouldShowNoResult || noResult) && (jsxs(DivLoaderSC, { children: [jsx(DivIconDataSC, { children: jsx(NoResultIcon, {}) }), jsx(PTitleSC$2, { children: noResultText })] })), noData && (jsxs(DivLoaderSC, { children: [jsx(DivIconDataSC, { children: jsx(NoDataIcon, {}) }), jsx(PTitleSC$2, { children: noDataText })] })), loading && (jsxs(DivLoaderSC, { children: [jsx(FISProgressCircular, { size: size, variant: "indeterminate" }), jsx(PTitleSC$2, { children: loadingText })] })), !shouldShowNoResult && !noData && !loading && (jsxs(Fragment, { children: [jsx(MenuContent, { "$removeSelectedGroup": !!removeSelectedGroup, children: groupsToRender.map((group, index) => (jsxs(DivWrapperSC$4, { children: [index !== 0 && jsx(FISMenuSection, { withDivider: true }), group?.groupLabel && (jsx(FISMenuSection, { label: group?.groupLabel })), group.items.map((item, idx) => (jsx(FISTooltip, { title: item.label, variant: "primary", children: jsx(FISMenuItem, { title: item.label, description: item.description, size: size, onClickMenu: () => handleItemClick(item), selected: selectedValues.includes(item.value), type: "select" }) }, idx)))] }, index))) }), removeSelectedGroup && (jsx(FixedBottomSection, { children: jsx(DivWrapperSC$4, { children: removeSelectedGroup.items.map((item, idx) => (jsx(FISTooltip, { title: item.label, variant: "primary", children: jsx(FISMenuItem, { title: item.label, description: item.description, size: size, onClickMenu: () => onChangeSelected?.([]), type: "select", iconPrefix: jsx(RemoveIcon, {}), negative: true }) }, idx))) }) }))] }))] }));
67481
67658
  };
67482
67659
  FISMenuSelect.displayName = "FISMenuSelect";
67483
67660