@quen-ui/components 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/Banner/Banner.cjs.js +96 -0
  2. package/dist/Banner/Banner.d.ts +4 -0
  3. package/dist/Banner/Banner.es.js +96 -0
  4. package/dist/Banner/index.d.ts +2 -0
  5. package/dist/Banner/styles.cjs.js +113 -0
  6. package/dist/Banner/styles.d.ts +12 -0
  7. package/dist/Banner/styles.es.js +113 -0
  8. package/dist/Banner/types.d.ts +40 -0
  9. package/dist/Dropdown/Dropdown.cjs.js +47 -43
  10. package/dist/Dropdown/Dropdown.d.ts +1 -1
  11. package/dist/Dropdown/Dropdown.es.js +48 -44
  12. package/dist/Dropdown/DropdownItem.cjs.js +10 -13
  13. package/dist/Dropdown/DropdownItem.es.js +10 -13
  14. package/dist/Dropdown/DropdownList.cjs.js +0 -2
  15. package/dist/Dropdown/DropdownList.es.js +0 -2
  16. package/dist/InputBase/InputBase.cjs.js +55 -12
  17. package/dist/InputBase/InputBase.d.ts +1 -1
  18. package/dist/InputBase/InputBase.es.js +57 -14
  19. package/dist/InputBase/styles.cjs.js +44 -4
  20. package/dist/InputBase/styles.d.ts +7 -1
  21. package/dist/InputBase/styles.es.js +45 -5
  22. package/dist/InputBase/types.d.ts +5 -1
  23. package/dist/InputDate/styles.d.ts +1 -1
  24. package/dist/InputNumber/styles.d.ts +1 -1
  25. package/dist/Select/Select.cjs.js +2 -5
  26. package/dist/Select/Select.es.js +2 -5
  27. package/dist/Select/helpers.d.ts +8 -0
  28. package/dist/Select/styles.d.ts +1 -1
  29. package/dist/Select/useSelect.d.ts +8 -0
  30. package/dist/Slider/Slider.cjs.js +122 -224
  31. package/dist/Slider/Slider.d.ts +1 -1
  32. package/dist/Slider/Slider.es.js +125 -227
  33. package/dist/Slider/styles.cjs.js +49 -32
  34. package/dist/Slider/styles.d.ts +1 -1
  35. package/dist/Slider/styles.es.js +49 -32
  36. package/dist/Slider/useSlider.cjs.js +194 -0
  37. package/dist/Slider/useSlider.d.ts +11 -0
  38. package/dist/Slider/useSlider.es.js +194 -0
  39. package/dist/Stepper/Step.cjs.js +114 -0
  40. package/dist/Stepper/Step.d.ts +2 -0
  41. package/dist/Stepper/Step.es.js +114 -0
  42. package/dist/Stepper/StepContent.cjs.js +10 -0
  43. package/dist/Stepper/StepContent.d.ts +4 -0
  44. package/dist/Stepper/StepContent.es.js +10 -0
  45. package/dist/Stepper/StepLabel.cjs.js +28 -0
  46. package/dist/Stepper/StepLabel.d.ts +2 -0
  47. package/dist/Stepper/StepLabel.es.js +28 -0
  48. package/dist/Stepper/Stepper.cjs.js +54 -0
  49. package/dist/Stepper/Stepper.d.ts +2 -0
  50. package/dist/Stepper/Stepper.es.js +54 -0
  51. package/dist/Stepper/StepperContext.cjs.js +13 -0
  52. package/dist/Stepper/StepperContext.d.ts +3 -0
  53. package/dist/Stepper/StepperContext.es.js +13 -0
  54. package/dist/Stepper/index.cjs.js +10 -0
  55. package/dist/Stepper/index.d.ts +12 -0
  56. package/dist/Stepper/index.es.js +11 -0
  57. package/dist/Stepper/styles.cjs.js +149 -0
  58. package/dist/Stepper/styles.d.ts +44 -0
  59. package/dist/Stepper/styles.es.js +149 -0
  60. package/dist/Stepper/types.d.ts +70 -0
  61. package/dist/TextField/TextField.cjs.js +4 -0
  62. package/dist/TextField/TextField.d.ts +1 -1
  63. package/dist/TextField/TextField.es.js +4 -0
  64. package/dist/TextField/styles.d.ts +1 -1
  65. package/dist/Textarea/styles.d.ts +1 -1
  66. package/dist/Tooltip/Tooltip.cjs.js +3 -1
  67. package/dist/Tooltip/Tooltip.es.js +4 -2
  68. package/dist/index.cjs.js +4 -0
  69. package/dist/index.d.ts +2 -0
  70. package/dist/index.es.js +4 -0
  71. package/package.json +1 -1
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const React = require("react");
5
+ const iconsReact = require("@tabler/icons-react");
6
+ const Title = require("../typography/Title/Title.cjs.js");
7
+ const Text = require("../typography/Text/Text.cjs.js");
8
+ const Button = require("../Button/Button.cjs.js");
9
+ const styles = require("./styles.cjs.js");
10
+ const Flex = require("../Flex/Flex.cjs.js");
11
+ const DEFAULT_ICONS = {
12
+ info: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconInfoCircle, {}),
13
+ success: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconRosetteDiscountCheck, {}),
14
+ warning: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconAlertTriangle, {}),
15
+ danger: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconAlertTriangle, {}),
16
+ neutral: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconInfoCircle, {})
17
+ };
18
+ const Banner = ({
19
+ variant = "info",
20
+ title,
21
+ description,
22
+ action,
23
+ onClose,
24
+ dismissible = false,
25
+ icon,
26
+ size = "m",
27
+ as = "div",
28
+ className,
29
+ children,
30
+ storageKey,
31
+ ...restProps
32
+ }) => {
33
+ const [isVisible, setIsVisible] = React.useState(true);
34
+ const [isMounted, setIsMounted] = React.useState(false);
35
+ React.useEffect(() => {
36
+ if (storageKey && typeof window !== "undefined") {
37
+ const dismissed = window.localStorage.getItem(`banner:${storageKey}`);
38
+ if (dismissed === "true") {
39
+ setIsVisible(false);
40
+ }
41
+ }
42
+ setIsMounted(true);
43
+ }, [storageKey]);
44
+ const handleClose = React.useCallback(() => {
45
+ setIsVisible(false);
46
+ onClose?.();
47
+ if (storageKey && typeof window !== "undefined") {
48
+ window.localStorage.setItem(`banner:${storageKey}`, "true");
49
+ }
50
+ }, [onClose, storageKey]);
51
+ if (!isMounted || !isVisible) return null;
52
+ const displayIcon = icon ?? DEFAULT_ICONS[variant];
53
+ const titleId = `banner-title-${title?.toString().slice(0, 8) || Date.now()}`;
54
+ const descId = `banner-desc-${Date.now()}`;
55
+ return /* @__PURE__ */ jsxRuntime.jsxs(
56
+ styles.BannerStyled,
57
+ {
58
+ as,
59
+ role: "region",
60
+ variant,
61
+ size,
62
+ visible: isVisible,
63
+ dismissible,
64
+ "aria-labelledby": title ? titleId : void 0,
65
+ "aria-describedby": description ? descId : void 0,
66
+ className,
67
+ ...restProps,
68
+ children: [
69
+ /* @__PURE__ */ jsxRuntime.jsxs(styles.BannerContent, { children: [
70
+ title && /* @__PURE__ */ jsxRuntime.jsxs(Flex, { align: "center", gap: "xs", children: [
71
+ displayIcon && /* @__PURE__ */ jsxRuntime.jsx(styles.BannerIcon, { "aria-hidden": "true", children: displayIcon }),
72
+ /* @__PURE__ */ jsxRuntime.jsx(Title, { size: "s", id: titleId, children: title })
73
+ ] }),
74
+ description && /* @__PURE__ */ jsxRuntime.jsxs(Flex, { align: "center", gap: "xs", children: [
75
+ displayIcon && !title && /* @__PURE__ */ jsxRuntime.jsx(styles.BannerIcon, { "aria-hidden": "true", children: displayIcon }),
76
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { id: descId, children: description })
77
+ ] })
78
+ ] }),
79
+ action && /* @__PURE__ */ jsxRuntime.jsx(styles.BannerAction, { children: action }),
80
+ dismissible && /* @__PURE__ */ jsxRuntime.jsx(
81
+ Button,
82
+ {
83
+ size: "s",
84
+ view: "icon",
85
+ onClick: handleClose,
86
+ "aria-label": "Close notification",
87
+ type: "button",
88
+ children: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconX, { width: 16, height: 16 })
89
+ }
90
+ )
91
+ ]
92
+ }
93
+ );
94
+ };
95
+ exports.Banner = Banner;
96
+ exports.default = Banner;
@@ -0,0 +1,4 @@
1
+ import { default as React } from 'react';
2
+ import { IBannerProps } from './types';
3
+ export declare const Banner: React.FC<IBannerProps>;
4
+ export default Banner;
@@ -0,0 +1,96 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect, useCallback } from "react";
3
+ import { IconX, IconInfoCircle, IconAlertTriangle, IconRosetteDiscountCheck } from "@tabler/icons-react";
4
+ import Title from "../typography/Title/Title.es.js";
5
+ import Text from "../typography/Text/Text.es.js";
6
+ import Button from "../Button/Button.es.js";
7
+ import { BannerStyled, BannerContent, BannerIcon, BannerAction } from "./styles.es.js";
8
+ import Flex from "../Flex/Flex.es.js";
9
+ const DEFAULT_ICONS = {
10
+ info: /* @__PURE__ */ jsx(IconInfoCircle, {}),
11
+ success: /* @__PURE__ */ jsx(IconRosetteDiscountCheck, {}),
12
+ warning: /* @__PURE__ */ jsx(IconAlertTriangle, {}),
13
+ danger: /* @__PURE__ */ jsx(IconAlertTriangle, {}),
14
+ neutral: /* @__PURE__ */ jsx(IconInfoCircle, {})
15
+ };
16
+ const Banner = ({
17
+ variant = "info",
18
+ title,
19
+ description,
20
+ action,
21
+ onClose,
22
+ dismissible = false,
23
+ icon,
24
+ size = "m",
25
+ as = "div",
26
+ className,
27
+ children,
28
+ storageKey,
29
+ ...restProps
30
+ }) => {
31
+ const [isVisible, setIsVisible] = useState(true);
32
+ const [isMounted, setIsMounted] = useState(false);
33
+ useEffect(() => {
34
+ if (storageKey && typeof window !== "undefined") {
35
+ const dismissed = window.localStorage.getItem(`banner:${storageKey}`);
36
+ if (dismissed === "true") {
37
+ setIsVisible(false);
38
+ }
39
+ }
40
+ setIsMounted(true);
41
+ }, [storageKey]);
42
+ const handleClose = useCallback(() => {
43
+ setIsVisible(false);
44
+ onClose?.();
45
+ if (storageKey && typeof window !== "undefined") {
46
+ window.localStorage.setItem(`banner:${storageKey}`, "true");
47
+ }
48
+ }, [onClose, storageKey]);
49
+ if (!isMounted || !isVisible) return null;
50
+ const displayIcon = icon ?? DEFAULT_ICONS[variant];
51
+ const titleId = `banner-title-${title?.toString().slice(0, 8) || Date.now()}`;
52
+ const descId = `banner-desc-${Date.now()}`;
53
+ return /* @__PURE__ */ jsxs(
54
+ BannerStyled,
55
+ {
56
+ as,
57
+ role: "region",
58
+ variant,
59
+ size,
60
+ visible: isVisible,
61
+ dismissible,
62
+ "aria-labelledby": title ? titleId : void 0,
63
+ "aria-describedby": description ? descId : void 0,
64
+ className,
65
+ ...restProps,
66
+ children: [
67
+ /* @__PURE__ */ jsxs(BannerContent, { children: [
68
+ title && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
69
+ displayIcon && /* @__PURE__ */ jsx(BannerIcon, { "aria-hidden": "true", children: displayIcon }),
70
+ /* @__PURE__ */ jsx(Title, { size: "s", id: titleId, children: title })
71
+ ] }),
72
+ description && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
73
+ displayIcon && !title && /* @__PURE__ */ jsx(BannerIcon, { "aria-hidden": "true", children: displayIcon }),
74
+ /* @__PURE__ */ jsx(Text, { id: descId, children: description })
75
+ ] })
76
+ ] }),
77
+ action && /* @__PURE__ */ jsx(BannerAction, { children: action }),
78
+ dismissible && /* @__PURE__ */ jsx(
79
+ Button,
80
+ {
81
+ size: "s",
82
+ view: "icon",
83
+ onClick: handleClose,
84
+ "aria-label": "Close notification",
85
+ type: "button",
86
+ children: /* @__PURE__ */ jsx(IconX, { width: 16, height: 16 })
87
+ }
88
+ )
89
+ ]
90
+ }
91
+ );
92
+ };
93
+ export {
94
+ Banner,
95
+ Banner as default
96
+ };
@@ -0,0 +1,2 @@
1
+ export { default as Banner } from './Banner';
2
+ export type { IBannerProps, TBannerVariant } from './types';
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const styled = require("styled-components");
4
+ const variantStyles = {
5
+ info: styled.css`
6
+ background: ${({ theme }) => theme.components.Banner.info.background};
7
+ border-color: ${({ theme }) => theme.components.Banner.info.borderColor};
8
+ color: ${({ theme }) => theme.components.Banner.info.color};
9
+ --banner-icon-color: ${({ theme }) => theme.components.Banner.info.iconColor};
10
+ `,
11
+ success: styled.css`
12
+ background: ${({ theme }) => theme.components.Banner.success.background};
13
+ border-color: ${({ theme }) => theme.components.Banner.success.borderColor};
14
+ color: ${({ theme }) => theme.components.Banner.success.color};
15
+ --banner-icon-color: ${({ theme }) => theme.components.Banner.success.iconColor};
16
+ `,
17
+ warning: styled.css`
18
+ background: ${({ theme }) => theme.components.Banner.warning.background};
19
+ border-color: ${({ theme }) => theme.components.Banner.warning.borderColor};
20
+ color: ${({ theme }) => theme.components.Banner.warning.color};
21
+ --banner-icon-color: ${({ theme }) => theme.components.Banner.warning.iconColor};
22
+ `,
23
+ danger: styled.css`
24
+ background: ${({ theme }) => theme.components.Banner.danger.background};
25
+ border-color: ${({ theme }) => theme.components.Banner.danger.borderColor};
26
+ color: ${({ theme }) => theme.components.Banner.danger.color};
27
+ --banner-icon-color: ${({ theme }) => theme.components.Banner.danger.iconColor};
28
+ `,
29
+ neutral: styled.css`
30
+ background: ${({ theme }) => theme.components.Banner.neutral.background};
31
+ border-color: ${({ theme }) => theme.components.Banner.neutral.borderColor};
32
+ color: ${({ theme }) => theme.components.Banner.neutral.color};
33
+ --banner-icon-color: ${({ theme }) => theme.components.Banner.neutral.iconColor};
34
+ `
35
+ };
36
+ const sizeStyles = {
37
+ xs: styled.css`
38
+ padding: ${({ theme }) => theme.space.xs};
39
+ gap: ${({ theme }) => theme.space.xs};
40
+ `,
41
+ s: styled.css`
42
+ padding: ${({ theme }) => theme.space.s};
43
+ gap: ${({ theme }) => theme.space.s};
44
+ `,
45
+ m: styled.css`
46
+ padding: ${({ theme }) => theme.space.m};
47
+ gap: ${({ theme }) => theme.space.m};
48
+ `,
49
+ l: styled.css`
50
+ padding: ${({ theme }) => theme.space.l};
51
+ gap: ${({ theme }) => theme.space.l};
52
+ `
53
+ };
54
+ const BannerStyled = styled.div`
55
+ display: grid;
56
+ grid-template-columns: auto 1fr auto auto;
57
+ align-items: center;
58
+ gap: ${({ size }) => size === "s" ? "10px" : size === "m" ? "14px" : "18px"};
59
+ border-left: 3px solid var(--banner-icon);
60
+ border-radius: ${({ theme }) => theme.control.radius};
61
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
62
+ transition:
63
+ opacity 0.2s ease,
64
+ transform 0.2s ease;
65
+
66
+ ${({ visible }) => !visible && styled.css`
67
+ opacity: 0;
68
+ transform: translateY(-8px);
69
+ pointer-events: none;
70
+ max-height: 0;
71
+ padding: 0;
72
+ margin: 0;
73
+ overflow: hidden;
74
+ `}
75
+
76
+ ${({ variant }) => variantStyles[variant]};
77
+ ${({ size }) => sizeStyles[size]};
78
+
79
+ @media (prefers-reduced-motion: reduce) {
80
+ transition: none !important;
81
+ }
82
+ `;
83
+ const BannerIcon = styled.div`
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: center;
87
+ color: var(--banner-icon);
88
+ flex-shrink: 0;
89
+ width: 1.2em;
90
+ height: 1.2em;
91
+ `;
92
+ const BannerContent = styled.div`
93
+ display: flex;
94
+ flex-direction: column;
95
+ gap: 2px;
96
+ min-width: 0;
97
+ `;
98
+ const BannerAction = styled.div`
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: flex-end;
102
+ margin-left: auto;
103
+ padding-left: 12px;
104
+
105
+ button,
106
+ a {
107
+ white-space: nowrap;
108
+ }
109
+ `;
110
+ exports.BannerAction = BannerAction;
111
+ exports.BannerContent = BannerContent;
112
+ exports.BannerIcon = BannerIcon;
113
+ exports.BannerStyled = BannerStyled;
@@ -0,0 +1,12 @@
1
+ import { TBannerVariant } from './types';
2
+ import { TQuenSize } from '../types/size';
3
+ interface IBannerStyledProps {
4
+ variant: TBannerVariant;
5
+ size: TQuenSize;
6
+ visible: boolean;
7
+ }
8
+ export declare const BannerStyled: import('styled-components/dist/types').IStyledComponentBase<"web", import('styled-components/dist/types').Substitute<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, IBannerStyledProps>> & string;
9
+ export declare const BannerIcon: import('styled-components/dist/types').IStyledComponentBase<"web", import('styled-components').FastOmit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
10
+ export declare const BannerContent: import('styled-components/dist/types').IStyledComponentBase<"web", import('styled-components').FastOmit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
11
+ export declare const BannerAction: import('styled-components/dist/types').IStyledComponentBase<"web", import('styled-components').FastOmit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
12
+ export {};
@@ -0,0 +1,113 @@
1
+ import styled, { css } from "styled-components";
2
+ const variantStyles = {
3
+ info: css`
4
+ background: ${({ theme }) => theme.components.Banner.info.background};
5
+ border-color: ${({ theme }) => theme.components.Banner.info.borderColor};
6
+ color: ${({ theme }) => theme.components.Banner.info.color};
7
+ --banner-icon-color: ${({ theme }) => theme.components.Banner.info.iconColor};
8
+ `,
9
+ success: css`
10
+ background: ${({ theme }) => theme.components.Banner.success.background};
11
+ border-color: ${({ theme }) => theme.components.Banner.success.borderColor};
12
+ color: ${({ theme }) => theme.components.Banner.success.color};
13
+ --banner-icon-color: ${({ theme }) => theme.components.Banner.success.iconColor};
14
+ `,
15
+ warning: css`
16
+ background: ${({ theme }) => theme.components.Banner.warning.background};
17
+ border-color: ${({ theme }) => theme.components.Banner.warning.borderColor};
18
+ color: ${({ theme }) => theme.components.Banner.warning.color};
19
+ --banner-icon-color: ${({ theme }) => theme.components.Banner.warning.iconColor};
20
+ `,
21
+ danger: css`
22
+ background: ${({ theme }) => theme.components.Banner.danger.background};
23
+ border-color: ${({ theme }) => theme.components.Banner.danger.borderColor};
24
+ color: ${({ theme }) => theme.components.Banner.danger.color};
25
+ --banner-icon-color: ${({ theme }) => theme.components.Banner.danger.iconColor};
26
+ `,
27
+ neutral: css`
28
+ background: ${({ theme }) => theme.components.Banner.neutral.background};
29
+ border-color: ${({ theme }) => theme.components.Banner.neutral.borderColor};
30
+ color: ${({ theme }) => theme.components.Banner.neutral.color};
31
+ --banner-icon-color: ${({ theme }) => theme.components.Banner.neutral.iconColor};
32
+ `
33
+ };
34
+ const sizeStyles = {
35
+ xs: css`
36
+ padding: ${({ theme }) => theme.space.xs};
37
+ gap: ${({ theme }) => theme.space.xs};
38
+ `,
39
+ s: css`
40
+ padding: ${({ theme }) => theme.space.s};
41
+ gap: ${({ theme }) => theme.space.s};
42
+ `,
43
+ m: css`
44
+ padding: ${({ theme }) => theme.space.m};
45
+ gap: ${({ theme }) => theme.space.m};
46
+ `,
47
+ l: css`
48
+ padding: ${({ theme }) => theme.space.l};
49
+ gap: ${({ theme }) => theme.space.l};
50
+ `
51
+ };
52
+ const BannerStyled = styled.div`
53
+ display: grid;
54
+ grid-template-columns: auto 1fr auto auto;
55
+ align-items: center;
56
+ gap: ${({ size }) => size === "s" ? "10px" : size === "m" ? "14px" : "18px"};
57
+ border-left: 3px solid var(--banner-icon);
58
+ border-radius: ${({ theme }) => theme.control.radius};
59
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
60
+ transition:
61
+ opacity 0.2s ease,
62
+ transform 0.2s ease;
63
+
64
+ ${({ visible }) => !visible && css`
65
+ opacity: 0;
66
+ transform: translateY(-8px);
67
+ pointer-events: none;
68
+ max-height: 0;
69
+ padding: 0;
70
+ margin: 0;
71
+ overflow: hidden;
72
+ `}
73
+
74
+ ${({ variant }) => variantStyles[variant]};
75
+ ${({ size }) => sizeStyles[size]};
76
+
77
+ @media (prefers-reduced-motion: reduce) {
78
+ transition: none !important;
79
+ }
80
+ `;
81
+ const BannerIcon = styled.div`
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ color: var(--banner-icon);
86
+ flex-shrink: 0;
87
+ width: 1.2em;
88
+ height: 1.2em;
89
+ `;
90
+ const BannerContent = styled.div`
91
+ display: flex;
92
+ flex-direction: column;
93
+ gap: 2px;
94
+ min-width: 0;
95
+ `;
96
+ const BannerAction = styled.div`
97
+ display: flex;
98
+ align-items: center;
99
+ justify-content: flex-end;
100
+ margin-left: auto;
101
+ padding-left: 12px;
102
+
103
+ button,
104
+ a {
105
+ white-space: nowrap;
106
+ }
107
+ `;
108
+ export {
109
+ BannerAction,
110
+ BannerContent,
111
+ BannerIcon,
112
+ BannerStyled
113
+ };
@@ -0,0 +1,40 @@
1
+ import { ReactNode, ElementType, HTMLAttributes } from 'react';
2
+ import { TQuenSize } from '../types/size';
3
+ export type TBannerVariant = 'info' | 'success' | 'warning' | 'danger' | 'neutral';
4
+ export interface IBannerProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
5
+ /**
6
+ * Unique key for storing the closure state in localStorage.
7
+ * If transferred, the banner will not appear again after closing.
8
+ */
9
+ storageKey?: string;
10
+ /**
11
+ * Visual style of the banner
12
+ * @default 'info'
13
+ */
14
+ variant?: TBannerVariant;
15
+ /** Banner title */
16
+ title?: ReactNode;
17
+ /** Description/banner content */
18
+ description?: ReactNode;
19
+ /** Action element (button, link) */
20
+ action?: ReactNode;
21
+ /** Callback when closing a banner */
22
+ onClose?: () => void;
23
+ /**
24
+ * Whether to show the close button
25
+ * @default false
26
+ */
27
+ dismissible?: boolean;
28
+ /** Custom icon (overrides the icon by variant) */
29
+ icon?: ReactNode;
30
+ /**
31
+ * Banner size
32
+ * @default 'm'
33
+ */
34
+ size?: TQuenSize;
35
+ /**
36
+ * Custom rendering element
37
+ * @default 'div'
38
+ */
39
+ as?: ElementType;
40
+ }
@@ -14,68 +14,72 @@ const Dropdown = ({
14
14
  width,
15
15
  anchorRef,
16
16
  onClickClose,
17
+ onClickOutside,
18
+ notCloseOutside,
17
19
  ...props
18
20
  }) => {
19
- const [anchorRect, setAnchorRect] = React.useState(helpers.DEFAULT_RECT_ELEMENT);
21
+ const [isOpen, setIsOpen] = React.useState(false);
22
+ const isControlled = typeof open !== "undefined";
23
+ const openState = isControlled ? open : isOpen;
20
24
  const dropdownRef = React.useRef(null);
21
25
  const [containerDropdown, setContainerDropdown] = React.useState(null);
26
+ const [anchorRect, setAnchorRect] = React.useState(helpers.DEFAULT_RECT_ELEMENT);
27
+ const lastInternalMousedownRef = React.useRef(0);
22
28
  const [state, toggle] = reactTransitionState.useTransitionState({
23
29
  timeout: 500,
24
30
  unmountOnExit: true,
25
- initialEntered: open
31
+ initialEntered: openState
26
32
  });
33
+ React.useEffect(() => {
34
+ if (isControlled) toggle(open);
35
+ }, [open, isControlled, toggle]);
36
+ const calculateAnchorRect = React.useCallback(() => {
37
+ if (anchorRef.current) {
38
+ setAnchorRect(helpers.calculateRectElement(anchorRef.current));
39
+ }
40
+ }, [anchorRef]);
41
+ const handleInternalMouseDown = React.useCallback(
42
+ (e) => {
43
+ lastInternalMousedownRef.current = e.timeStamp || Date.now();
44
+ },
45
+ []
46
+ );
27
47
  hooks.useOnClickOutside(
28
- anchorRef,
29
- () => {
30
- if (typeof open === "undefined") {
31
- toggle(false);
32
- onClickClose?.();
33
- }
48
+ [anchorRef, dropdownRef],
49
+ (e) => {
50
+ if (e.timeStamp - lastInternalMousedownRef.current < 2100) return;
51
+ onClickOutside?.();
52
+ onClickClose?.();
53
+ if (!isControlled) setIsOpen(false);
34
54
  },
35
- {
36
- excludeRef: dropdownRef
37
- }
55
+ { isActive: !notCloseOutside && openState }
38
56
  );
39
- const calculateAnchorRect = () => {
40
- if (anchorRef && anchorRef.current) {
41
- setAnchorRect(helpers.calculateRectElement(anchorRef.current));
42
- }
43
- };
44
- const handleClickAnchorRef = () => {
45
- toggle();
46
- };
57
+ React.useEffect(() => {
58
+ if (isControlled || !anchorRef.current) return;
59
+ const el = anchorRef.current;
60
+ const handleClick = () => {
61
+ toggle();
62
+ setIsOpen((prev) => !prev);
63
+ };
64
+ el.addEventListener("click", handleClick);
65
+ return () => el.removeEventListener("click", handleClick);
66
+ }, [anchorRef, toggle, isControlled]);
67
+ React.useLayoutEffect(() => {
68
+ calculateAnchorRect();
69
+ }, [state, calculateAnchorRect]);
47
70
  React.useEffect(() => {
48
71
  window.addEventListener("resize", calculateAnchorRect);
49
72
  window.addEventListener("scroll", calculateAnchorRect, true);
50
73
  return () => {
51
74
  window.removeEventListener("resize", calculateAnchorRect);
52
- window.addEventListener("scroll", calculateAnchorRect, true);
53
- };
54
- }, [anchorRect]);
55
- React.useEffect(() => {
56
- if (typeof open === "undefined") {
57
- anchorRef.current?.addEventListener("click", handleClickAnchorRef);
58
- }
59
- return () => {
60
- anchorRef.current?.removeEventListener("click", handleClickAnchorRef);
75
+ window.removeEventListener("scroll", calculateAnchorRect, true);
61
76
  };
62
- }, [anchorRef]);
63
- React.useEffect(() => {
64
- if (typeof open !== "undefined") {
65
- toggle(open);
66
- }
67
- }, [open]);
68
- React.useLayoutEffect(() => {
69
- calculateAnchorRect();
70
- }, [state]);
77
+ }, [calculateAnchorRect]);
71
78
  React.useEffect(() => {
72
- const container = document.getElementsByTagName("body").item(0);
73
- setContainerDropdown(container);
79
+ if (typeof document !== "undefined") setContainerDropdown(document.body);
74
80
  }, []);
75
- if (disabled) {
76
- return null;
77
- }
78
- return /* @__PURE__ */ jsxRuntime.jsx(styles.DropdownWrapper, { children: state.isEnter && containerDropdown && reactDom.createPortal(
81
+ if (disabled) return null;
82
+ return /* @__PURE__ */ jsxRuntime.jsx(styles.DropdownWrapper, { onMouseDown: handleInternalMouseDown, children: state.isEnter && containerDropdown && reactDom.createPortal(
79
83
  /* @__PURE__ */ jsxRuntime.jsx(
80
84
  DropdownPortal,
81
85
  {
@@ -1,4 +1,4 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { IDropdownProps } from './types';
3
- declare const Dropdown: <ITEM>({ disabled, open, direction, width, anchorRef, onClickClose, ...props }: IDropdownProps<ITEM>) => ReactNode;
3
+ declare const Dropdown: <ITEM>({ disabled, open, direction, width, anchorRef, onClickClose, onClickOutside, notCloseOutside, ...props }: IDropdownProps<ITEM>) => ReactNode;
4
4
  export default Dropdown;