@redsift/popovers 8.0.0 → 8.0.1

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 (185) hide show
  1. package/coverage/clover.xml +738 -0
  2. package/coverage/coverage-final.json +53 -0
  3. package/coverage/lcov-report/Tooltip.tsx.html +235 -0
  4. package/coverage/lcov-report/TooltipContent.tsx.html +235 -0
  5. package/coverage/lcov-report/TooltipTrigger.tsx.html +241 -0
  6. package/coverage/lcov-report/base.css +224 -0
  7. package/coverage/lcov-report/block-navigation.js +87 -0
  8. package/coverage/lcov-report/context.ts.html +97 -0
  9. package/coverage/lcov-report/dialog/Dialog.tsx.html +271 -0
  10. package/coverage/lcov-report/dialog/context.ts.html +97 -0
  11. package/coverage/lcov-report/dialog/index.html +191 -0
  12. package/coverage/lcov-report/dialog/index.ts.html +100 -0
  13. package/coverage/lcov-report/dialog/types.ts.html +253 -0
  14. package/coverage/lcov-report/dialog/useDialog.tsx.html +346 -0
  15. package/coverage/lcov-report/dialog/useDialogContext.tsx.html +121 -0
  16. package/coverage/lcov-report/dialog-content/DialogContent.tsx.html +487 -0
  17. package/coverage/lcov-report/dialog-content/index.html +146 -0
  18. package/coverage/lcov-report/dialog-content/index.ts.html +91 -0
  19. package/coverage/lcov-report/dialog-content/intl/index.html +116 -0
  20. package/coverage/lcov-report/dialog-content/intl/index.ts.html +106 -0
  21. package/coverage/lcov-report/dialog-content/styles.ts.html +301 -0
  22. package/coverage/lcov-report/dialog-content-actions/DialogContentActions.tsx.html +205 -0
  23. package/coverage/lcov-report/dialog-content-actions/index.html +146 -0
  24. package/coverage/lcov-report/dialog-content-actions/index.ts.html +91 -0
  25. package/coverage/lcov-report/dialog-content-actions/styles.ts.html +139 -0
  26. package/coverage/lcov-report/dialog-content-body/DialogContentBody.tsx.html +232 -0
  27. package/coverage/lcov-report/dialog-content-body/index.html +146 -0
  28. package/coverage/lcov-report/dialog-content-body/index.ts.html +91 -0
  29. package/coverage/lcov-report/dialog-content-body/styles.ts.html +259 -0
  30. package/coverage/lcov-report/dialog-content-header/DialogContentHeader.tsx.html +328 -0
  31. package/coverage/lcov-report/dialog-content-header/index.html +146 -0
  32. package/coverage/lcov-report/dialog-content-header/index.ts.html +91 -0
  33. package/coverage/lcov-report/dialog-content-header/styles.ts.html +193 -0
  34. package/coverage/lcov-report/dialog-trigger/DialogTrigger.tsx.html +214 -0
  35. package/coverage/lcov-report/dialog-trigger/index.html +131 -0
  36. package/coverage/lcov-report/dialog-trigger/index.ts.html +91 -0
  37. package/coverage/lcov-report/favicon.png +0 -0
  38. package/coverage/lcov-report/index.html +341 -0
  39. package/coverage/lcov-report/index.ts.html +97 -0
  40. package/coverage/lcov-report/popover/Popover.tsx.html +244 -0
  41. package/coverage/lcov-report/popover/context.ts.html +97 -0
  42. package/coverage/lcov-report/popover/index.html +191 -0
  43. package/coverage/lcov-report/popover/index.ts.html +100 -0
  44. package/coverage/lcov-report/popover/types.ts.html +244 -0
  45. package/coverage/lcov-report/popover/usePopover.tsx.html +295 -0
  46. package/coverage/lcov-report/popover/usePopoverContext.tsx.html +121 -0
  47. package/coverage/lcov-report/popover-content/PopoverContent.tsx.html +268 -0
  48. package/coverage/lcov-report/popover-content/index.html +146 -0
  49. package/coverage/lcov-report/popover-content/index.ts.html +91 -0
  50. package/coverage/lcov-report/popover-content/styles.ts.html +166 -0
  51. package/coverage/lcov-report/popover-trigger/PopoverTrigger.tsx.html +214 -0
  52. package/coverage/lcov-report/popover-trigger/index.html +131 -0
  53. package/coverage/lcov-report/popover-trigger/index.ts.html +91 -0
  54. package/coverage/lcov-report/prettify.css +1 -0
  55. package/coverage/lcov-report/prettify.js +2 -0
  56. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  57. package/coverage/lcov-report/sorter.js +196 -0
  58. package/coverage/lcov-report/styles.ts.html +391 -0
  59. package/coverage/lcov-report/toast/Toast.tsx.html +451 -0
  60. package/coverage/lcov-report/toast/index.html +161 -0
  61. package/coverage/lcov-report/toast/index.ts.html +91 -0
  62. package/coverage/lcov-report/toast/intl/index.html +116 -0
  63. package/coverage/lcov-report/toast/intl/index.ts.html +106 -0
  64. package/coverage/lcov-report/toast/styles.ts.html +214 -0
  65. package/coverage/lcov-report/toast/types.ts.html +217 -0
  66. package/coverage/lcov-report/toast-container/ToastContainer.tsx.html +217 -0
  67. package/coverage/lcov-report/toast-container/index.html +161 -0
  68. package/coverage/lcov-report/toast-container/index.ts.html +94 -0
  69. package/coverage/lcov-report/toast-container/styles.ts.html +2323 -0
  70. package/coverage/lcov-report/toast-container/useToast.tsx.html +469 -0
  71. package/coverage/lcov-report/toast-provider/ToastProvider.tsx.html +256 -0
  72. package/coverage/lcov-report/toast-provider/context.ts.html +106 -0
  73. package/coverage/lcov-report/toast-provider/index.html +161 -0
  74. package/coverage/lcov-report/toast-provider/index.ts.html +97 -0
  75. package/coverage/lcov-report/toast-provider/useToast.tsx.html +109 -0
  76. package/coverage/lcov-report/tooltip/Tooltip.tsx.html +250 -0
  77. package/coverage/lcov-report/tooltip/context.ts.html +97 -0
  78. package/coverage/lcov-report/tooltip/index.html +191 -0
  79. package/coverage/lcov-report/tooltip/index.ts.html +100 -0
  80. package/coverage/lcov-report/tooltip/types.ts.html +250 -0
  81. package/coverage/lcov-report/tooltip/useTooltip.tsx.html +352 -0
  82. package/coverage/lcov-report/tooltip/useTooltipContext.tsx.html +121 -0
  83. package/coverage/lcov-report/tooltip-content/TooltipContent.tsx.html +313 -0
  84. package/coverage/lcov-report/tooltip-content/index.html +146 -0
  85. package/coverage/lcov-report/tooltip-content/index.ts.html +91 -0
  86. package/coverage/lcov-report/tooltip-content/styles.ts.html +337 -0
  87. package/coverage/lcov-report/tooltip-trigger/TooltipTrigger.tsx.html +211 -0
  88. package/coverage/lcov-report/tooltip-trigger/index.html +131 -0
  89. package/coverage/lcov-report/tooltip-trigger/index.ts.html +91 -0
  90. package/coverage/lcov-report/types.ts.html +226 -0
  91. package/coverage/lcov-report/useTooltip.tsx.html +277 -0
  92. package/coverage/lcov-report/useTooltipContext.tsx.html +121 -0
  93. package/coverage/lcov-report/useTooltipOpen.tsx.html +559 -0
  94. package/coverage/lcov.info +1330 -0
  95. package/dist/package.json +96 -0
  96. package/index.ts +1 -0
  97. package/jest.config.js +3 -0
  98. package/package.json +2 -3
  99. package/rollup.config.js +13 -0
  100. package/src/components/dialog/Dialog.stories.tsx +201 -0
  101. package/src/components/dialog/Dialog.test.tsx +116 -0
  102. package/src/components/dialog/Dialog.tsx +62 -0
  103. package/src/components/dialog/context.ts +4 -0
  104. package/src/components/dialog/index.ts +5 -0
  105. package/src/components/dialog/types.ts +56 -0
  106. package/src/components/dialog/useDialog.tsx +87 -0
  107. package/src/components/dialog/useDialogContext.tsx +12 -0
  108. package/src/components/dialog-content/DialogContent.stories.tsx +337 -0
  109. package/src/components/dialog-content/DialogContent.tsx +134 -0
  110. package/src/components/dialog-content/index.ts +2 -0
  111. package/src/components/dialog-content/intl/en-US.json +3 -0
  112. package/src/components/dialog-content/intl/fr-FR.json +3 -0
  113. package/src/components/dialog-content/intl/index.ts +7 -0
  114. package/src/components/dialog-content/styles.ts +72 -0
  115. package/src/components/dialog-content/types.ts +11 -0
  116. package/src/components/dialog-content-actions/DialogContentActions.test.tsx +68 -0
  117. package/src/components/dialog-content-actions/DialogContentActions.tsx +40 -0
  118. package/src/components/dialog-content-actions/index.ts +2 -0
  119. package/src/components/dialog-content-actions/styles.ts +18 -0
  120. package/src/components/dialog-content-actions/types.ts +11 -0
  121. package/src/components/dialog-content-body/DialogContentBody.test.tsx +63 -0
  122. package/src/components/dialog-content-body/DialogContentBody.tsx +49 -0
  123. package/src/components/dialog-content-body/index.ts +2 -0
  124. package/src/components/dialog-content-body/styles.ts +58 -0
  125. package/src/components/dialog-content-body/types.ts +14 -0
  126. package/src/components/dialog-content-header/DialogContentHeader.test.tsx +63 -0
  127. package/src/components/dialog-content-header/DialogContentHeader.tsx +81 -0
  128. package/src/components/dialog-content-header/index.ts +2 -0
  129. package/src/components/dialog-content-header/styles.ts +36 -0
  130. package/src/components/dialog-content-header/types.ts +23 -0
  131. package/src/components/dialog-trigger/DialogTrigger.tsx +43 -0
  132. package/src/components/dialog-trigger/index.ts +2 -0
  133. package/src/components/dialog-trigger/types.ts +9 -0
  134. package/src/components/popover/Popover.stories.tsx +158 -0
  135. package/src/components/popover/Popover.test.tsx +102 -0
  136. package/src/components/popover/Popover.tsx +53 -0
  137. package/src/components/popover/context.ts +4 -0
  138. package/src/components/popover/index.ts +5 -0
  139. package/src/components/popover/types.ts +53 -0
  140. package/src/components/popover/usePopover.tsx +70 -0
  141. package/src/components/popover/usePopoverContext.tsx +12 -0
  142. package/src/components/popover-content/PopoverContent.tsx +61 -0
  143. package/src/components/popover-content/index.ts +2 -0
  144. package/src/components/popover-content/styles.ts +27 -0
  145. package/src/components/popover-content/types.ts +11 -0
  146. package/src/components/popover-trigger/PopoverTrigger.tsx +43 -0
  147. package/src/components/popover-trigger/index.ts +2 -0
  148. package/src/components/popover-trigger/types.ts +9 -0
  149. package/src/components/toast/Toast.stories.tsx +75 -0
  150. package/src/components/toast/Toast.test.tsx +63 -0
  151. package/src/components/toast/Toast.tsx +122 -0
  152. package/src/components/toast/index.ts +2 -0
  153. package/src/components/toast/intl/en-US.json +3 -0
  154. package/src/components/toast/intl/fr-FR.json +3 -0
  155. package/src/components/toast/intl/index.ts +7 -0
  156. package/src/components/toast/styles.ts +43 -0
  157. package/src/components/toast/types.ts +44 -0
  158. package/src/components/toast-container/ToastContainer.stories.tsx +349 -0
  159. package/src/components/toast-container/ToastContainer.tsx +44 -0
  160. package/src/components/toast-container/index.ts +3 -0
  161. package/src/components/toast-container/styles.ts +746 -0
  162. package/src/components/toast-container/types.ts +110 -0
  163. package/src/components/toast-container/useToast.test.tsx +111 -0
  164. package/src/components/toast-container/useToast.tsx +128 -0
  165. package/src/components/tooltip/Tooltip.stories.tsx +200 -0
  166. package/src/components/tooltip/Tooltip.test.tsx +119 -0
  167. package/src/components/tooltip/Tooltip.tsx +55 -0
  168. package/src/components/tooltip/context.ts +4 -0
  169. package/src/components/tooltip/index.ts +5 -0
  170. package/src/components/tooltip/types.ts +55 -0
  171. package/src/components/tooltip/useTooltip.tsx +89 -0
  172. package/src/components/tooltip/useTooltipContext.tsx +12 -0
  173. package/src/components/tooltip-content/TooltipContent.tsx +76 -0
  174. package/src/components/tooltip-content/index.ts +2 -0
  175. package/src/components/tooltip-content/styles.ts +84 -0
  176. package/src/components/tooltip-content/types.ts +14 -0
  177. package/src/components/tooltip-trigger/TooltipTrigger.tsx +42 -0
  178. package/src/components/tooltip-trigger/index.ts +2 -0
  179. package/src/components/tooltip-trigger/types.ts +9 -0
  180. package/src/index.ts +14 -0
  181. package/tsconfig.json +3 -0
  182. /package/{CONTRIBUTING.md → dist/CONTRIBUTING.md} +0 -0
  183. /package/{index.d.ts → dist/index.d.ts} +0 -0
  184. /package/{index.js → dist/index.js} +0 -0
  185. /package/{index.js.map → dist/index.js.map} +0 -0
@@ -0,0 +1,102 @@
1
+ import React from 'react';
2
+ import { fireEvent, render, screen } from '@testing-library/react';
3
+
4
+ import { Button } from '@redsift/design-system';
5
+ import { PopoverContent } from '../popover-content';
6
+ import { PopoverTrigger } from '../popover-trigger';
7
+ import { Popover } from '.';
8
+
9
+ describe('Popover', () => {
10
+ const onOpenSpy = jest.fn();
11
+ const realError = console.error;
12
+
13
+ beforeEach(() => {
14
+ console.error = jest.fn();
15
+ });
16
+
17
+ afterEach(() => {
18
+ onOpenSpy.mockClear();
19
+ console.error = realError;
20
+ });
21
+
22
+ it.each`
23
+ Name | Component | props
24
+ ${'PopoverTrigger'} | ${PopoverTrigger} | ${{}}
25
+ ${'PopoverContent'} | ${PopoverContent} | ${{}}
26
+ `(
27
+ '$Name should throw error when not wrapped inside `Popover`',
28
+ function ({ Component, props }) {
29
+ expect(() => render(<Component {...props} />)).toThrow(
30
+ 'Popover components must be wrapped in <Popover />'
31
+ );
32
+ }
33
+ );
34
+
35
+ it.each`
36
+ Name | Component | props
37
+ ${'Controlled Popover'} | ${Popover} | ${{ onOpen: onOpenSpy, isOpen: false }}
38
+ ${'Uncontrolled Popover'} | ${Popover} | ${{ onOpen: onOpenSpy, defaultOpen: false }}
39
+ `(
40
+ '$Name can be closed by default and then open',
41
+ function ({ Name, Component, props }) {
42
+ const { getByText } = render(
43
+ <Component {...props}>
44
+ <PopoverTrigger>
45
+ <Button variant="secondary">Trigger</Button>
46
+ </PopoverTrigger>
47
+ <PopoverContent>Content</PopoverContent>
48
+ </Component>
49
+ );
50
+
51
+ expect(screen.queryByText('Content')).not.toBeInTheDocument();
52
+ expect(onOpenSpy).not.toHaveBeenCalled();
53
+
54
+ const triggerButton = getByText('Trigger');
55
+ fireEvent.click(triggerButton);
56
+
57
+ expect(onOpenSpy).toHaveBeenCalled();
58
+ if (Name.includes('Uncontrolled')) {
59
+ expect(getByText('Content')).toBeVisible();
60
+
61
+ fireEvent.click(triggerButton);
62
+
63
+ expect(screen.queryByText('Content')).not.toBeInTheDocument();
64
+ expect(onOpenSpy).toHaveBeenCalled();
65
+ }
66
+ }
67
+ );
68
+
69
+ it.each`
70
+ Name | Component | props
71
+ ${'Controlled Popover'} | ${Popover} | ${{ onOpen: onOpenSpy, isOpen: true }}
72
+ ${'Uncontrolled Popover'} | ${Popover} | ${{ onOpen: onOpenSpy, defaultOpen: true }}
73
+ `(
74
+ '$Name can be open by default and then closed',
75
+ function ({ Name, Component, props }) {
76
+ const { getByText } = render(
77
+ <Component {...props}>
78
+ <PopoverTrigger>
79
+ <Button variant="secondary">Trigger</Button>
80
+ </PopoverTrigger>
81
+ <PopoverContent>Content</PopoverContent>
82
+ </Component>
83
+ );
84
+
85
+ expect(getByText('Content')).toBeVisible();
86
+ expect(onOpenSpy).not.toHaveBeenCalled();
87
+
88
+ const triggerButton = getByText('Trigger');
89
+ fireEvent.click(triggerButton);
90
+
91
+ expect(onOpenSpy).toHaveBeenCalled();
92
+ if (Name.includes('Uncontrolled')) {
93
+ expect(screen.queryByText('Content')).not.toBeInTheDocument();
94
+
95
+ fireEvent.click(triggerButton);
96
+
97
+ expect(getByText('Content')).toBeVisible();
98
+ expect(onOpenSpy).toHaveBeenCalled();
99
+ }
100
+ }
101
+ );
102
+ });
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { partitionComponents, isComponent } from '@redsift/design-system';
3
+ import { PopoverContent } from '../popover-content';
4
+ import { PopoverTrigger } from '../popover-trigger';
5
+
6
+ import { PopoverContext } from './context';
7
+ import { PopoverPlacement, PopoverProps } from './types';
8
+ import { usePopover } from './usePopover';
9
+
10
+ const COMPONENT_NAME = 'Popover';
11
+ const CLASSNAME = 'redsift-popover';
12
+ const DEFAULT_PROPS: Partial<PopoverProps> = {
13
+ isModal: false,
14
+ placement: PopoverPlacement.bottom,
15
+ };
16
+
17
+ /**
18
+ * The Popover component.
19
+ */
20
+ export const BasePopover: React.FC<PopoverProps> & {
21
+ displayName?: string;
22
+ className?: string;
23
+ } = (props) => {
24
+ const { children, defaultOpen, placement, isModal, isOpen, onOpen } = props;
25
+
26
+ const popover = usePopover({
27
+ defaultOpen,
28
+ placement,
29
+ isModal,
30
+ isOpen,
31
+ onOpen,
32
+ });
33
+
34
+ const [[trigger], [content]] = partitionComponents(
35
+ React.Children.toArray(children),
36
+ [isComponent('PopoverTrigger'), isComponent('PopoverContent')]
37
+ );
38
+
39
+ return (
40
+ <PopoverContext.Provider value={popover}>
41
+ {trigger}
42
+ {content}
43
+ </PopoverContext.Provider>
44
+ );
45
+ };
46
+ BasePopover.className = CLASSNAME;
47
+ BasePopover.defaultProps = DEFAULT_PROPS;
48
+ BasePopover.displayName = COMPONENT_NAME;
49
+
50
+ export const Popover = Object.assign(BasePopover, {
51
+ Trigger: PopoverTrigger,
52
+ Content: PopoverContent,
53
+ });
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import { PopoverState } from './types';
3
+
4
+ export const PopoverContext = React.createContext<PopoverState | null>(null);
@@ -0,0 +1,5 @@
1
+ export * from './context';
2
+ export * from './types';
3
+ export * from './Popover';
4
+ export * from './usePopover';
5
+ export * from './usePopoverContext';
@@ -0,0 +1,53 @@
1
+ import { ReactNode } from 'react';
2
+ import { ValueOf } from '@redsift/design-system';
3
+ import { usePopover } from './usePopover';
4
+
5
+ /**
6
+ * Context props.
7
+ */
8
+ export type PopoverState = ReturnType<typeof usePopover> | null;
9
+
10
+ /**
11
+ * Component variant.
12
+ */
13
+ export const PopoverPlacement = {
14
+ top: 'top',
15
+ right: 'right',
16
+ bottom: 'bottom',
17
+ left: 'left',
18
+ 'top-start': 'top-start',
19
+ 'top-end': 'top-end',
20
+ 'right-start': 'right-start',
21
+ 'right-end': 'right-end',
22
+ 'bottom-start': 'bottom-start',
23
+ 'bottom-end': 'bottom-end',
24
+ 'left-start': 'left-start',
25
+ 'left-end': 'left-end',
26
+ } as const;
27
+ export type PopoverPlacement = ValueOf<typeof PopoverPlacement>;
28
+
29
+ /**
30
+ * Component props.
31
+ */
32
+ export interface PopoverProps {
33
+ /** Children. Can only be PopoverTrigger and PopoverContent. */
34
+ children: ReactNode;
35
+ /**
36
+ * Default open status.
37
+ * Used for uncontrolled version.
38
+ */
39
+ defaultOpen?: boolean;
40
+ /** Default placement of the popover. */
41
+ placement?: PopoverPlacement;
42
+ /** Whether the popover is a modal or not. */
43
+ isModal?: boolean;
44
+ /**
45
+ * Whether the component is opened or not.
46
+ * Used for controlled version.
47
+ */
48
+ isOpen?: boolean;
49
+ /** Method to handle component change. */
50
+ onOpen?: (open: boolean) => void;
51
+ }
52
+
53
+ export type StyledPopoverProps = PopoverProps;
@@ -0,0 +1,70 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import {
3
+ useFloating,
4
+ autoUpdate,
5
+ offset,
6
+ flip,
7
+ shift,
8
+ useDismiss,
9
+ useRole,
10
+ useInteractions,
11
+ } from '@floating-ui/react';
12
+ import { PopoverProps } from './types';
13
+
14
+ export function usePopover({
15
+ defaultOpen,
16
+ placement,
17
+ isModal,
18
+ isOpen: propsIsOpen,
19
+ onOpen,
20
+ }: Omit<PopoverProps, 'children'>) {
21
+ const [isOpen, setIsOpen] = useState(propsIsOpen ?? defaultOpen);
22
+
23
+ useEffect(() => {
24
+ setIsOpen(propsIsOpen ?? defaultOpen);
25
+ }, [propsIsOpen, defaultOpen]);
26
+
27
+ const handleOpen = useCallback(
28
+ (collapsed: boolean) => {
29
+ if (onOpen) {
30
+ onOpen(collapsed);
31
+ }
32
+ if (propsIsOpen === undefined || propsIsOpen === null) {
33
+ setIsOpen(collapsed);
34
+ }
35
+ },
36
+ [onOpen]
37
+ );
38
+
39
+ const data = useFloating({
40
+ placement,
41
+ open: isOpen,
42
+ onOpenChange: handleOpen,
43
+ whileElementsMounted: autoUpdate,
44
+ middleware: [
45
+ offset(2),
46
+ flip({
47
+ fallbackAxisSideDirection: 'end',
48
+ }),
49
+ shift({ padding: 2 }),
50
+ ],
51
+ });
52
+
53
+ const context = data.context;
54
+
55
+ const dismiss = useDismiss(context);
56
+ const role = useRole(context);
57
+
58
+ const interactions = useInteractions([dismiss, role]);
59
+
60
+ return React.useMemo(
61
+ () => ({
62
+ isOpen,
63
+ handleOpen,
64
+ ...interactions,
65
+ ...data,
66
+ isModal,
67
+ }),
68
+ [isOpen, handleOpen, interactions, data, isModal]
69
+ );
70
+ }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { PopoverContext } from './context';
3
+
4
+ export const usePopoverContext = () => {
5
+ const context = React.useContext(PopoverContext);
6
+
7
+ if (context == null) {
8
+ throw new Error('Popover components must be wrapped in <Popover />');
9
+ }
10
+
11
+ return context;
12
+ };
@@ -0,0 +1,61 @@
1
+ import React, { forwardRef } from 'react';
2
+ import {
3
+ useMergeRefs,
4
+ FloatingPortal,
5
+ FloatingFocusManager,
6
+ } from '@floating-ui/react';
7
+
8
+ import { Comp } from '@redsift/design-system';
9
+ import { PopoverContentProps } from './types';
10
+ import { usePopoverContext } from '../popover';
11
+ import { StyledPopoverContent } from './styles';
12
+ import classNames from 'classnames';
13
+
14
+ const COMPONENT_NAME = 'PopoverContent';
15
+ const CLASSNAME = 'redsift-popover-content';
16
+ const DEFAULT_PROPS: Partial<PopoverContentProps> = {};
17
+
18
+ /**
19
+ * The PopoverContent component.
20
+ */
21
+ export const PopoverContent: Comp<PopoverContentProps, HTMLDivElement> =
22
+ forwardRef((props, ref) => {
23
+ const { children, className, style } = props;
24
+ const {
25
+ context: floatingContext,
26
+ getFloatingProps,
27
+ isModal,
28
+ isOpen,
29
+ refs,
30
+ strategy,
31
+ x,
32
+ y,
33
+ } = usePopoverContext();
34
+ const popoverRef = useMergeRefs([refs.setFloating, ref]);
35
+
36
+ return (
37
+ <FloatingPortal id="redsift-app-container">
38
+ {isOpen && (
39
+ <FloatingFocusManager context={floatingContext} modal={isModal}>
40
+ <StyledPopoverContent
41
+ className={classNames(PopoverContent.className, className)}
42
+ ref={popoverRef}
43
+ style={{
44
+ position: strategy,
45
+ top: y ?? 0,
46
+ left: x ?? 0,
47
+ width: 'fit-content',
48
+ ...style,
49
+ }}
50
+ {...getFloatingProps(props)}
51
+ >
52
+ {children}
53
+ </StyledPopoverContent>
54
+ </FloatingFocusManager>
55
+ )}
56
+ </FloatingPortal>
57
+ );
58
+ });
59
+ PopoverContent.className = CLASSNAME;
60
+ PopoverContent.defaultProps = DEFAULT_PROPS;
61
+ PopoverContent.displayName = COMPONENT_NAME;
@@ -0,0 +1,2 @@
1
+ export * from './types';
2
+ export * from './PopoverContent';
@@ -0,0 +1,27 @@
1
+ import styled from 'styled-components';
2
+ import { baseContainer } from '@redsift/design-system';
3
+ import { StyledPopoverContentProps } from './types';
4
+
5
+ /**
6
+ * Component style.
7
+ */
8
+ export const StyledPopoverContent = styled.div<StyledPopoverContentProps>`
9
+ ${baseContainer}
10
+ align-items: center;
11
+ background-color: var(--redsift-color-neutral-white);
12
+ box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2);
13
+ color: var(--redsift-color-neutral-black);
14
+ display: flex;
15
+ filter: drop-shadow(0px 4px 5px rgba(0, 0, 0, 0.14))
16
+ drop-shadow(0px 1px 10px rgba(0, 0, 0, 0.12));
17
+ font-family: var(--redsift-typography-popover-font-family);
18
+ font-size: var(--redsift-typography-popover-font-size);
19
+ font-weight: var(--redsift-typography-popover-font-weight);
20
+ line-height: var(--redsift-typography-popover-line-height);
21
+ max-width: calc(100vw - 48px);
22
+ z-index: var(--redsift-layout-z-index-popover);
23
+
24
+ &:focus-visible {
25
+ outline: none;
26
+ }
27
+ `;
@@ -0,0 +1,11 @@
1
+ import { ComponentProps } from 'react';
2
+ import { ContainerProps, InternalSpacingProps } from '@redsift/design-system';
3
+
4
+ /**
5
+ * Component props.
6
+ */
7
+ export interface PopoverContentProps
8
+ extends ComponentProps<'div'>,
9
+ Omit<ContainerProps, keyof InternalSpacingProps> {}
10
+
11
+ export type StyledPopoverContentProps = PopoverContentProps;
@@ -0,0 +1,43 @@
1
+ import React, { forwardRef, ReactElement } from 'react';
2
+ import { useMergeRefs } from '@floating-ui/react';
3
+
4
+ import { Comp, isComponent } from '@redsift/design-system';
5
+ import { usePopoverContext } from '../popover';
6
+ import { PopoverTriggerProps } from './types';
7
+
8
+ const COMPONENT_NAME = 'PopoverTrigger';
9
+ const CLASSNAME = 'redsift-popover-trigger';
10
+ const DEFAULT_PROPS: Partial<PopoverTriggerProps> = {};
11
+
12
+ /**
13
+ * The PopoverTrigger component.
14
+ */
15
+ export const PopoverTrigger: Comp<PopoverTriggerProps, HTMLButtonElement> =
16
+ forwardRef((props, ref) => {
17
+ const { children } = props;
18
+
19
+ const { getReferenceProps, isOpen, handleOpen, refs } = usePopoverContext();
20
+ const childrenRef = (children as any).ref;
21
+ const triggerRef = useMergeRefs([refs.setReference, ref, childrenRef]);
22
+
23
+ if (
24
+ isComponent('Button')(children) ||
25
+ isComponent('IconButton')(children) ||
26
+ isComponent('LinkButton')(children)
27
+ ) {
28
+ return React.cloneElement(children, {
29
+ ...getReferenceProps({
30
+ ref: triggerRef,
31
+ ...props,
32
+ ...(children as ReactElement).props,
33
+ }),
34
+ onClick: () => handleOpen(!isOpen),
35
+ isActive: isOpen,
36
+ });
37
+ }
38
+
39
+ return <>{children}</>;
40
+ });
41
+ PopoverTrigger.className = CLASSNAME;
42
+ PopoverTrigger.defaultProps = DEFAULT_PROPS;
43
+ PopoverTrigger.displayName = COMPONENT_NAME;
@@ -0,0 +1,2 @@
1
+ export * from './types';
2
+ export * from './PopoverTrigger';
@@ -0,0 +1,9 @@
1
+ import { ComponentProps } from 'react';
2
+ import { StylingProps } from '@redsift/design-system';
3
+
4
+ /**
5
+ * Component props.
6
+ */
7
+ export interface PopoverTriggerProps
8
+ extends Omit<ComponentProps<'button'>, 'onClick'>,
9
+ StylingProps {}
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+
3
+ import { Toast, ToastVariant } from '.';
4
+ import { Flexbox } from '@redsift/design-system';
5
+ import { Button, ButtonColor } from '@redsift/design-system';
6
+
7
+ export default {
8
+ title: 'Popovers/Toast/Toast',
9
+ component: Toast,
10
+ };
11
+
12
+ export const Simple = () => (
13
+ <Toast>Cupcake ipsum dolor sit amet pastry tootsie roll sugar plum.</Toast>
14
+ );
15
+
16
+ export const WithTitle = () => (
17
+ <Toast title="Title">
18
+ Cupcake ipsum dolor sit amet pastry tootsie roll sugar plum.
19
+ </Toast>
20
+ );
21
+
22
+ export const AllVariants = () => (
23
+ <Flexbox flexDirection="column">
24
+ {Object.keys(ToastVariant).map((variant) => (
25
+ <Toast
26
+ key={variant}
27
+ variant={variant as ToastVariant}
28
+ title={`${variant[0].toUpperCase()}${variant.slice(1, variant.length)}`}
29
+ >
30
+ Cupcake ipsum dolor sit amet pastry tootsie roll sugar plum.
31
+ </Toast>
32
+ ))}
33
+ </Flexbox>
34
+ );
35
+
36
+ export const WithoutCloseButton = () => (
37
+ <Toast title="Without close button" closeButton={false}>
38
+ Cupcake ipsum dolor sit amet pastry tootsie roll sugar plum.
39
+ </Toast>
40
+ );
41
+
42
+ export const WithCustomCloseButton = () => (
43
+ <Flexbox flexDirection="column">
44
+ {Object.keys(ToastVariant).map((variant) => (
45
+ <Toast
46
+ key={variant}
47
+ variant={variant as ToastVariant}
48
+ title={`${variant[0].toUpperCase()}${variant.slice(1, variant.length)}`}
49
+ closeButton={<Button>Click me</Button>}
50
+ >
51
+ Cupcake ipsum dolor sit amet pastry tootsie roll sugar plum.
52
+ </Toast>
53
+ ))}
54
+ </Flexbox>
55
+ );
56
+
57
+ export const WithComponents = () => (
58
+ <Flexbox flexDirection="column">
59
+ {Object.keys(ToastVariant).map((variant) => (
60
+ <Toast
61
+ key={variant}
62
+ variant={variant as ToastVariant}
63
+ title={`${variant[0].toUpperCase()}${variant.slice(1, variant.length)}`}
64
+ >
65
+ Cupcake ipsum dolor sit amet pastry tootsie roll sugar plum.
66
+ <Button
67
+ color={variant === 'loading' ? 'default' : (variant as ButtonColor)}
68
+ marginTop="8px"
69
+ >
70
+ Click me
71
+ </Button>
72
+ </Toast>
73
+ ))}
74
+ </Flexbox>
75
+ );
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import { fireEvent, render } from '@testing-library/react';
3
+
4
+ import { Toast } from '.';
5
+ import { Button } from '@redsift/design-system';
6
+
7
+ describe('Toast', () => {
8
+ const onClickSpy = jest.fn();
9
+ const onClickSpy2 = jest.fn();
10
+
11
+ afterEach(() => {
12
+ onClickSpy.mockClear();
13
+ onClickSpy2.mockClear();
14
+ });
15
+
16
+ it('handles default', () => {
17
+ const { getByText } = render(<Toast>Content</Toast>);
18
+ expect(getByText('Content')).toBeVisible();
19
+ });
20
+
21
+ it('supports custom className', () => {
22
+ const tree = render(<Toast className="test-class">Content</Toast>);
23
+ const component = tree.asFragment().firstChild;
24
+ expect(component).toHaveAttribute(
25
+ 'class',
26
+ expect.stringContaining(Toast.className!)
27
+ );
28
+ expect(component).toHaveAttribute(
29
+ 'class',
30
+ expect.stringContaining('test-class')
31
+ );
32
+ });
33
+
34
+ it('supports custom data attributes', () => {
35
+ const tree = render(<Toast data-testid="test">Content</Toast>);
36
+ const component = tree.asFragment().firstChild;
37
+ expect(component).toHaveAttribute('data-testid', 'test');
38
+ });
39
+
40
+ it('supports custom close button with onClick', () => {
41
+ const { getByRole } = render(
42
+ <Toast closeButton={<Button onClick={onClickSpy}>Click me</Button>}>
43
+ Cupcake ipsum dolor sit amet pastry tootsie roll sugar plum.
44
+ </Toast>
45
+ );
46
+
47
+ const button = getByRole('button');
48
+ fireEvent.click(button);
49
+ expect(onClickSpy).toHaveBeenCalled();
50
+ });
51
+
52
+ it('supports custom close button without onClick', () => {
53
+ const { getByRole } = render(
54
+ <Toast closeToast={onClickSpy2} closeButton={<Button>Click me</Button>}>
55
+ Cupcake ipsum dolor sit amet pastry tootsie roll sugar plum.
56
+ </Toast>
57
+ );
58
+
59
+ const button = getByRole('button');
60
+ fireEvent.click(button);
61
+ expect(onClickSpy2).toHaveBeenCalled();
62
+ });
63
+ });